VERSION = 3.00!Arial, 0, 9, 5, 15, 12, 32, 3, 0 x_frminternalsystrayuseonlyPixelsClass1formx_frminternalsystrayuseonlyr YYIM%Uy?| SetWindowLong USER32.DLLQWinAPI_SetWindowLongC TTU SETWINDOWLONGUSER32DLLWINAPI_SETWINDOWLONGTHISHWNDWIDTHHEIGHTInit,1r1$)Y%Top = 0 Left = 0 Height = 34 Width = 123 ShowWindow = 2 ShowInTaskBar = .F. DoCreate = .T. BorderStyle = 0 Caption = "" ControlBox = .F. Closable = .F. MaxButton = .F. MinButton = .F. Movable = .F. MaxHeight = 1 TitleBar = 0 Themes = .F. Name = "x_frminternalsystrayuseonly" formsystrayiconidentifier^ iconcount^ enabled^ nprevmwaccrual^ integertostring^ loadicon^ aiconlist^ tiptext_assign^ stringtointeger^ iconfile_assign^ GoBack^ GoForward^ NavigateTo^ PixelsClass1 hyperlinksystraytiptext Text to be displayed in standard tooltip above the Taskbar Notification Area ("systray"). menutext Defines a menu to be displayed when user clicks on icon. Specify a numeric ID and text for each item, separating each by a semicolon. (e.g. 1;Activate; 2; Exit). The ID is passed to the ProcessMenuEvent method after the user has made a menu selecti menutextismpr Set to true if the MenuText property contains the name of a MPR to run. The MPR must define a shortcut menu. To pass parameters to the MPR, call ShowMenu method from the IconClick event. iconidentifier Used internally to store a unique id for our icon. This way, multiple instances of this class can be used without interfering with each other. iconcount systrayiconid Contains Unique ID for the systray token that this object creates. currenticonindex Determines which of the currently loaded icons is displayed in the Taskbar Notification Area ("systray"). enabled Is .T. when we have an icon in the Taskbar Notification Area ("systray"). iconfile If only one icon is used, it can be specified here. If multiple icons are to be used (animation), use the AddIconToIconList method. menuoffsetfromright Essentially the width of your menu in pixels. This is an offset used when the TaskBar is docked on the right, to prevent the menu from being drawn underneath the taskbar. addicontosystrayatinit If .T., the icon will be added to the Taskbar Notification Area ("systray") when this class is instantiated. If .F., you must call the AddIconToSystray() method. shellversion Contains the numeric version of the Shell communication method that we've established. nprevmwaccrual Previous setting of SYS(2060). *receiveiconevent Receives the events the Notify Icon. *setforegroundwindow By default, brings the form that contains this object to the foreground. You can also specify a different window to bring forward by passing a numeric hWnd parameter. *cleariconlist Clear all icons from the current list. If an icon is currently displayed in the taskbar notification area (systray), it is removed. *addicontoiconlist Adds an icon file to list of icons. Accepts a string parameter containing a filename. Call the SwitchIcon method to switch to the next icon. *switchicon Changes the displayed icon to the next icon in the icon list. Use AddIconToIconList method to populate the icon list, and ClearIconList to start over. *addicontosystray Adds the icon to the Taskbar Notification Area ("systray"). *removeiconfromsystray Removes the icon from the Taskbar Notification Area ("systray"). *showmenu Displays the menu defined by the MenuText property, or runs an MPR that defines a shortcut menu. Accepts string parameter containing the filename of the MPR to run, and optional second parameter to be passed to the MPR startup code. *processmenuevent This event is called if user has selected an item from a menu defined by the MenuText property. Add code here to act upon the user's selection. *iconclickevent Occurs when the user clicks on the icon in the Taskbar Notification Area ("systray"). *integertostring Converts an integer to its binary representation. *loadicon Loads an Icon from a file, and returns an hIcon token. ^aiconlist[1,0] Array containnig Icons and handles. *tiptext_assign *currenticonindex_assign *stringtointeger Convert binary representation of integer into an integer data type. *gettaskbarposition Retrieves coordinates of TaskBar. Also returns 0 if docked on bottom, 1 if docked left, 2 if docked top, 3 if docked right. *getshellversion Determines the Major and Minor version of shell32.dll. Used to determine if the shell supports balloon tips on taskbar icons. *balloonclickevent Occurs when user clicks on balloon tip. *balloonshowevent Occurs when the BalloonTip is shown. Balloon tips are queued by the system, so you balloon tip may not be displayed immediately after calling the DisplayBallloonTip method. *balloonhideevent Occurs if the user clicks on the close button of the balloon tip. *balloontimeoutevent Occurs if the system closes the balloon tip due to reaching the timeout. *iconrightclickevent Occurs when the user right-clicks on the icon in the Taskbar Notification Area ("systray"). *iconmiddleclickevent Occurs when the user clicks the icon using the middle mouse button. *icondblclickevent Occurs when the user double-clicks on the icon in the Taskbar Notification Area. *iconfile_assign *showballoontip Displays a balloon tip over the icon in the Taskbar Notification Area. Parameters: cBalloonText [, cBalloonTitle [, nIcon [, nTimeoutInSeconds]]] /PROCEDURE Init * For proper menu behavior, this form must be a Top-Level form, not a Desktop form. * But Top-Level forms are required to have a titlebar, which can be visible from * beneath the menu. Therefore, we'll make this window invisible, before it is * even displayed. #DEFINE GWL_EXSTYLE -20 *#DEFINE WS_EX_LAYERED 0x00080000 #DEFINE WS_EX_TRANSPARENT 0x00000020 DECLARE SetWindowLong IN USER32.DLL AS WinAPI_SetWindowLong Integer, Integer, Integer *DECLARE SetLayeredWindowAttributes In Win32Api Integer, String, Integer, Integer WinAPI_SetWindowLong(THIS.hWnd, GWL_EXSTYLE, WS_EX_TRANSPARENT) *SetLayeredWindowAttributes(THIS.hWnd, 0, 0, 2) * Make window as small as possible so menu position will be more accurate. THIS.Width = 1 THIS.Height = 1 ENDPROC 2G .2.2%*0,Uq%.B-T9%j% Hx$ C  *       B {  B   B =  B ]  B } B  B2Bf Hb  *       B 6  B V  B2bBU NDIRECTIONNSHIFTNXCOORDNYCOORDTHIS SYSTRAYICONIDLEFT SHELLVERSIONICONCLICKEVENTICONDBLCLICKEVENTICONRIGHTCLICKEVENTICONMIDDLECLICKEVENTBALLOONSHOWEVENTBALLOONHIDEEVENTBALLOONTIMEOUTEVENTBALLOONCLICKEVENT%Cm.lhWndbN~H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC BB%CO% Ta%T%9  )%9%T9H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC U LHWNDSETFOREGROUNDWINDOWUSER32DLLWINAPI_SETFOREGROUNDWINDOWTHISFORMVISIBLE WINDOWSTATE SHOWWINDOWHWND %#B8| DestroyIcon user32.dllQWinAPI_DestroyIcon(CCC TTU THIS ICONCOUNT DESTROYICONUSER32DLLWINAPI_DESTROYICON NCURRENTICON AICONLISTCURRENTICONINDEX%C0 B-TC % SB- TT %TBaU CICONFILENAMENHANDLETHISLOADICON ICONCOUNT AICONLISTCURRENTICONINDEXt m%_%@T[T BUTHIS ICONCOUNTCURRENTICONINDEX: *%  ; BF|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon% T ? T TCC TC  TCC    TC  TCC 'TCCC_ = C % TC TC TCC QTC TCC @QTCC TCC> % TC %i B5TC9 MouseWheelreceiveIconEvent Ta%CTCC TC  TCC    TC  TCC #TCCC_=C TC TC TCC QTC TCC @QTCC TCC> TC %C BT%C>?TC % BT&TC %"1C% * Modifying item in tray failed!!!x B BULCNOTIFYICONDATATHIS ICONCOUNTCURRENTICONINDEXSHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICON SHELLVERSIONNTIPTEXTMAXLENGTHINTEGERTOSTRINGHWND SYSTRAYICONID AICONLISTTIPTEXTENABLEDNRETURNGETSHELLVERSION}F|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon Q.C9 MouseWheelreceiveIconEvent T-TCCTC TCC   TC TC#TCCC _?=@C TCC>T C T U SHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICONCNOTIFYICONDATATHISENABLEDINTEGERTOSTRINGHWND SYSTRAYICONIDTIPTEXTNRETURN SHELLVERSION     QFORMG|GetForegroundWindow user32.dllQWinAPI_GetForegroundWindowTC;| GetCursorPos user32.dllQWinAPI_GetCursorPosTCC QT CT CC =T CC \ T T T T!TCT  systray.vcx T a+ .TCx_frmInternalSystrayUseOnly T -% systray.vcxc(T CCC] systray.vcx T T - Hz T T  T T  NT T 2zT T   C"! H/ CtCm.lcMPRFileNamebC %Ct  < #$T#p1%(CtV"T# #, pC $_*DO (m.lcMPRFileName) WITH &lcParamList < & C'0  '<2()*+,-.T(C *'a;% (/<B-%C (G% (oT((<B-T)C]5/ T/s )CCW+( (T,CC + *gT-C + *T.C + zs. )" -[T0ON SELECTION BAR C ._ OF C )_ __systray_menu_return = C ,_&lcOnSelectionBar G t )<C/1 B/C !U2 LCMPRFILENAMEP1P2P3P4P5P6LHPREVIOUSWINDOWLCPOINTLNRETURNLNXCOORDLNYCOORD LCCLASSLIB LLCONTINUE LNTASKBARLEFT LNTASKBARTOPLNTASKBARRIGHTLNTASKBARBOTTOMLNTASKBARLOCATION LOTEMPFORMGETFOREGROUNDWINDOWUSER32DLLWINAPI_GETFOREGROUNDWINDOW GETCURSORPOSWINAPI_GETCURSORPOSTHISSTRINGTOINTEGERGETTASKBARPOSITIONTOPLEFTMENUOFFSETFROMRIGHTSHOWSETFOREGROUNDWINDOWHWND LCPARAMLISTLNCOUNTNCOUNT MENUTEXTISMPRMENUTEXT LNMENUITEMS LCMENUNAME LAMENUITEMSLNCURRENTMENUITEMLNTOKENLCTEXTLNBARNUM__SYSTRAY_MENU_RETURNLCONSELECTIONBARPROCESSMENUEVENT U NMENUITEMID(%CZ ! CUTHISMENUTEXTSHOWMENUQGBCC CCC CCC CCC UNINTEGERt>| LoadImage user32.dllQWinAPI_LoadImage%C05TCCtemp5 systray_temp_C].icn(B#T CTCTALKv SET TALK &cOldTalk % b?CFailed to load icon ''. in Ct   B U CICONFILENAMECOLDTALK CTEMPFILENAMEHIMAGE LOADIMAGEUSER32DLLWINAPI_LOADIMAGETHISERROR?TC %8 CUVNEWVALTHISTIPTEXTENABLEDADDICONTOSYSTRAY:T %3 UVNEWVALTHISCURRENTICONINDEXENABLEDADDICONTOSYSTRAY %Ct ) T  T( 2T CC \  BULCPDWORDLNBYTESLNRETURN LNCURBYTED|SHAppBarMessage shell32.dllQWinAPI_SHAppBarMessage  LTC TC TC TCC QTC TCC> T C TCC\ %XT ATCC\ %T ATCC\ %T ATCC\ %HT A H](     B    B    B2 BU NLEFTNTOPNRIGHTNBOTTOMSHAPPBARMESSAGESHELL32DLLWINAPI_SHAPPBARMESSAGE CAPPBARDATANRETURNTHISINTEGERTOSTRINGSTRINGTOINTEGERF| DllGetVersion shell32.dllQWinAPI_Shell32_DllGetVersion TCT CC QCTCC \ TCC  \ *BCC Z.CC ZgU DLLGETVERSIONSHELL32DLLWINAPI_SHELL32_DLLGETVERSIONLCDLLVERSIONINFOLNMAJORLNMINORTHISINTEGERTOSTRINGSTRINGTOINTEGER(%CZ ! CUTHISMENUTEXTSHOWMENU<T CC UVNEWVALTHISICONFILE CLEARICONLISTADDICONTOICONLIST%Ct . B% P B#%Cm.lcBalloonTextbC B$%Cm.lcBalloonTitlebC T%Cm.lnIconbN T%C m.lnTimeoutbN T/T   TCC TC "TCC    TC TCC #TCCC _=C TCTC%TCCC _=C TC $TCCC _?=@C TCCTCC>TC U LCBALLOONTEXTLCBALLOONTITLELNICON LNTIMEOUTTHIS SHELLVERSIONLCNOTIFYICONDATALNRETURNINTEGERTOSTRINGHWND SYSTRAYICONID AICONLISTCURRENTICONINDEXTIPTEXTWINAPI_SHELL_NOTIFYICON41%C THIS.IconFilebC C> DC)%C_screen.nSysTrayCountbUC nSysTrayCount9%9?T9T99T9@TCC ]gC ]%- U THISICONFILEADDICONTOICONLIST ADDPROPERTY NSYSTRAYCOUNT SYSTRAYICONIDNPREVMWACCRUALADDICONTOSYSTRAYATINITADDICONTOSYSTRAYr k%1 C C%9gT9C ]UTHISENABLEDREMOVEICONFROMSYSTRAY CLEARICONLIST NSYSTRAYCOUNTNPREVMWACCRUALreceiveiconevent,setforegroundwindow cleariconlistaddicontoiconlist switchiconaddicontosystrayremoveiconfromsystrayshowmenuprocessmenueventKiconclickeventfintegertostringloadicon tiptext_assigncurrenticonindex_assign`stringtointegergettaskbarpositiongetshellversion-"iconrightclickevent#iconfile_assign$showballoontip~$Init (Destroy)11FrA1"DAAA!A!A!A!A!ABA!A!A!ABAAB4qAABTACA1AAA5!BAQAA5qqAs1qA1q!AAr7!11AAA4qAgBADqq!aaaASAR4Eq1aaaSADQAAARAAB3f}Cqa1AR3PquA!AAA1!a1c!1!"!A1qq!AqAq1qqA2qqAArQAarB32q4DA4qt4qQ!AA74a3A2qBA2q#A3A!A21K!aaaRAAAAAAAABA@1b!A3DA3q"131ABA2ABAAAAB!q1aaQAUA31AAAAQqZA3A1AA3, 7V lNN[vmx91167N6NON8?OPS<=PfQ]?Q(VfQMV[WWWkX]XZhGZccf fg#gh,h0t6Ktz}z|).2|$PROCEDURE receiveiconevent LPARAMETERS nDirection, nShift, nXCoord, nYCoord * This is the procedure that receives the Notify Icon events. Since * we execute the BindEvent() function from within this class, this * procedure can be hidden. * nDirection contains our icon identifier. IF nDirection != this.SystrayIconID * Not our icon, or this a real MouseWheel event on the VFP window. RETURN .F. ENDIF * These are the events that we receive from the Taskbar icon. #DEFINE WM_MOUSEMOVE 0x0200 #DEFINE WM_LBUTTONDOWN 0x0201 #DEFINE WM_LBUTTONUP 0x0202 #DEFINE WM_LBUTTONDBLCLK 0x0203 #DEFINE WM_RBUTTONDOWN 0x0204 #DEFINE WM_RBUTTONUP 0x0205 #DEFINE WM_RBUTTONDBLCLK 0x0206 #DEFINE WM_MBUTTONDOWN 0x0207 #DEFINE WM_MBUTTONUP 0x0208 #DEFINE WM_MBUTTONDBLCLK 0x0209 * Mousewheel events also get passed, but are difficult to decipher. #define WM_CONTEXTMENU 0x007B && Same as RightClick, but used when && Version 5 events have been specified. && (See the DisplayBalloonTip method.) #define WM_USER 0x0400 #define NIN_SELECT WM_USER + 0 #define NINF_KEY 0x1 #define NIN_KEYSELECT BITOR(NIN_SELECT , NINF_KEY) * Balloon events supported on Windows ME and Windows XP, and later. Not supported on Win2k. #DEFINE NIN_BALLOONSHOW (WM_USER + 2) #DEFINE NIN_BALLOONHIDE (WM_USER + 3) #DEFINE NIN_BALLOONTIMEOUT (WM_USER + 4) #DEFINE NIN_BALLOONUSERCLICK (WM_USER + 5) * Even though this isn't a real MouseWheel event on the VFP window, * VFP gives us the coordinates with respect to the location of the * main VFP window. Must change back to global coordinates. nXCoord = nXCoord + _screen.Left IF nXCoord <> WM_MOUSEMOVE && Ignore mousemove events. * The Version 5 events are more difficult to deal with, * but Version 5 supports Balloon Tips. So the extra work * is worth it. IF THIS.ShellVersion >= 5 DO CASE CASE nXCoord = NIN_SELECT OR nXCoord = NIN_KEYSELECT THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_CONTEXTMENU && Shell version 5 and later THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN CASE nXCoord = NIN_BALLOONSHOW THIS.BalloonShowEvent RETURN CASE nXCoord = NIN_BALLOONHIDE THIS.BalloonHideEvent RETURN CASE nXCoord = NIN_BALLOONTIMEOUT THIS.BalloonTimeoutEvent RETURN CASE nXCoord = NIN_BALLOONUSERCLICK This.BalloonClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ELSE * Using Version 4 events. DO CASE CASE nXCoord = WM_LBUTTONDOWN THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_RBUTTONDOWN THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ENDIF ENDIF ENDPROC PROCEDURE setforegroundwindow LPARAMETERS lhWnd * Brings the specified top-level window to the top of the Windows z-order. * If no parameter specified, then THISFORM is made the foreground window. IF TYPE("m.lhWnd") = "N" DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(m.lhWnd) RETURN ENDIF RETURN * If we're here, then the user called this method without an * hWnd parameter. Assume it is thisform that is to be brought forward. IF VARTYPE(thisform) == "O" IF ! thisform.Visible thisform.Visible = .t. ENDIF * First make sure it isn't minimized. IF thisform.WindowState= 1 thisform.WindowState = 0 ENDIF * If necessary, make sure the main VFP window is not minimized. IF _screen.Visible AND thisform.ShowWindow = 0 IF _screen.WindowState = 1 _screen.WindowState = 0 && Set back to normal ENDIF ENDIF DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(thisform.HWnd) ENDIF ENDPROC PROCEDURE cleariconlist * Removes all icons from the icon list. * In detail: * Releases the resource handles of all icons that have been loaded, * clears the array and sets the count to zero. This method is called * by the Destroy event, to make sure all system resources are correctly * released. WITH this IF .IconCount = 0 * No icons in list RETURN ENDIF * Have to release all icon resources DECLARE INTEGER DestroyIcon IN user32.dll AS WinAPI_DestroyIcon ; INTEGER hIcon FOR nCurrentIcon = 1 TO ALEN(.aIconList,1) WinAPI_DestroyIcon(.aIconList[m.nCurrentIcon]) ENDFOR DIMENSION .aIconList[1] .IconCount = 0 .CurrentIconIndex = 0 ENDWITH ENDPROC PROCEDURE addicontoiconlist LPARAMETERS cIconFileName IF !FILE(cIconFileName) RETURN .f. ENDIF * We must load the icon as a Windows resource. nHandle = THIS.LoadIcon(m.cIconFileName) IF m.nHandle = 0 RETURN .f. ENDIF * We have a resource handle for the icon. Now we store that * handle (integer) in our array of icon resource handles. WITH THIS .IconCount = .IconCount + 1 DIMENSION .aIconList[.IconCount] .aIconList[.IconCount] = m.nHandle IF .CurrentIconIndex = 0 .CurrentIconIndex = 1 && Updates Systray immediately ENDIF ENDWITH RETURN .T. ENDPROC PROCEDURE switchicon * Switches to the next icon in the iconlist. * Typically called by a timer to regularly change the icon, * to create animation. WITH THIS IF .IconCount > 1 IF .CurrentIconIndex = .iconCount .CurrentIconIndex = 1 ELSE .CurrentIconIndex = .CurrentIconIndex + 1 ENDIF ENDIF RETURN .CurrentIconIndex ENDWITH ENDPROC PROCEDURE addicontosystray *!* This is where the magic happens. *!* This is an unabashed hack to allow us to get the full functionality *!* of the Taskbar Notification Area ("System Tray") without having to *!* use an external C++ library. *!* To communicate with the systray, we tell it to send us messages via *!* the MouseWheel event. (This is the only VFP event that doesn't alter *!* or discard event data before firing the corresponding internal event.) *!* The second trick is that only the main VFP window will accept the events *!* without checking to see if the event coordinates are invalid. (The screen *!* doesn't even need to be visible, so you can have SCREEN=OFF in your *!* config.fpw file.) So we use VFP8's BINDEVENT() function to bind to the *!* _SCREEN.MouseWheel event. *!* This method sets up that communication path. *!* Returns 1 if successful. LOCAL lcNotifyIconData ** NOTIFYICON struct defines #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 && Win2k and later. #define NIF_INFO 0x00000010 && Use balloon tip. Win2k and later. && Notify Icon Infotip flags #define NIIF_NONE 0x00000000 && icon flags are mutually exclusive && and take only the lowest 2 bits #define NIIF_INFO 0x00000001 #define NIIF_WARNING 0x00000002 #define NIIF_ERROR 0x00000003 #define NIIF_ICON_MASK 0x0000000F #define NIIF_NOSOUND 0x00000010 && Windows XP and later. #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIM_SETFOCUS 0x00000003 && Windows 2000 and later. #define NIM_SETVERSION 0x00000004 && Windows 2000 and later. #define NOTIFYICON_VERSION 3 #define NIS_HIDDEN 0x00000001 #define NIS_SHAREDICON 0x00000002 WITH THIS * If no icons loaded, do nothing. IF .IconCount < 1 OR .CurrentIconIndex = 0 RETURN 0 ENDIF * Declare the WinAPI function that lets us install the icon. * We redeclare the function every time, just in case CLEAR DLLS is called * elsewhere in the app. DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; STRING @ PNOTIFYICONDATA IF THIS.ShellVersion = 0 && If we haven't set the version yet. nTipTextMaxLength = 63 && Default to version 4. ELSE nTipTextMaxLength = 127 ENDIF * Build NOTIFYICONDATA structure. lcNotifyIconData = .IntegerToString(_VFP.Hwnd) && Messages get sent to VFP's main window. lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), nTipTextMaxLength ), nTipTextMaxLength + 1, CHR(0)) && TipText IF .ShellVersion >= 5 lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags ENDIF lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure IF NOT .Enabled && If not already in Taskbar Notification Area.... * Add icon to Taskbar Notification Area. nReturn = WinAPI_Shell_NotifyIcon( NIM_ADD, @lcNotifyIconData) IF nReturn <> 1 * Adding item to tray failed!!! RETURN nReturn ENDIF * Bind to the MouseWheel event on VFP's main window. nReturn = BINDEVENT(_screen,"MouseWheel",this,"receiveIconEvent",2) .Enabled = .t. * We've just added the icon. Now we see if we can set to Version 5 shell behavior, * with larger tooltip lengths and support for balloon Tips. IF .GetShellVersion() >= 5 * This OS supports version 5 features. Switch to version 5. * We have to inform the Shell that we want to use Version 5 features. * Build a version 5 structure for sending message to change version. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * First we check to see if we can switch to using version 5 events. nReturn = WinAPI_Shell_NotifyIcon( NIM_SETVERSION, @lcNotifyIconData) IF nReturn <> 1 * Couldn't switch to version 5 events RETURN -1 ENDIF .ShellVersion = 5 * Now that we've updated to Version 5, we have to modify the existing icon * if the .TipText is longer than 63 characters. IF LEN(.TipText) > 63 nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 RETURN nReturn ENDIF ENDIF ELSE .ShellVersion = 4 ENDIF ELSE * Already in the Taskbar Notification Area. Just update existing icon. nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 MESSAGEBOX(" * Modifying item in tray failed!!!") RETURN nReturn ENDIF ENDIF ENDWITH RETURN nReturn ENDPROC PROCEDURE removeiconfromsystray * Removes the icon from the Taskbar Notification Area ('System Tray'). DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; string @ PNOTIFYICONDATA #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 #define NIF_INFO 0x00000010 LOCAL cNotifyIconData WITH THIS UNBINDEVENTS(_screen, "MouseWheel", this, "receiveIconEvent") .Enabled = .f. * Build structure. cNotifyIconData = .IntegerToString(_vfp.hwnd) cNotifyIconData = cNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) cNotifyIconData = cNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE, ,NIF_TIP cNotifyIconData = cNotifyIconData + .IntegerToString(0x20A) && uCallback cNotifyIconData = cNotifyIconData + .IntegerToString(0) && icon handle cNotifyIconData = cNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 63), 64, CHR(0)) cNotifyIconData = .integertostring(LEN(cNotifyIconData) + 4) + cNotifyIconData ENDWITH nReturn = WinAPI_Shell_NotifyIcon(NIM_DELETE, @cNotifyIconData) THIS.ShellVersion = 0 ENDPROC PROCEDURE showmenu LPARAMETERS lcMPRFileName, p1, p2, p3, p4, p5, p6 * Displays the shortcut menu near the taskbar icon. * If the menu is defined as a set of tokens and text strings, the * menu is defined here. Otherwise, the specified MPR is called. * If the first parameter is blank or not specified, the contents * of the MenuText property are used to define/determine the menu. * If the first parameter is not blank, it is assumed to specify * the MPR to run. The MenuText property is ignored. * * The additional parameters are passed on to the MPR. They are only * used if the cMPRFileName parameter is specified and non-blank. * * In order to display the shortcut menu in the proper location, * we must activate it within a toplevel form (with .visible = .t.), * near the taskbar. * On WindowsME, Win2k and later, the form is made invisible by * calling a Windows API function. (See the INIT of the form class.) * But on Win98 and NT4, that API function didn't exist, so we make it * a "Desktop" form instead. This allows us to remove the titlebar and * make the form so small it won't be noticed. But it causes slightly * incorrect behavior when trying to clear the menu. LOCAL lhPreviousWindow, lcPOINT, lnReturn, lnXCoord, lnYCoord, lcClassLib, llContinue LOCAL lnTaskBarLeft, lnTaskBarTop, lnTaskBarRight, lnTaskBarBottom, lnTaskBarLocation LOCAL loTempForm as Form * Our invisible form must be made the top window in order to make the shortcut * menu get cleared properly if user clicks elsewhere. Therefore, we store the * handle of the current foreground window, so we can restore it later. DECLARE INTEGER GetForegroundWindow IN user32.dll AS WinAPI_GetForegroundWindow lhPreviousWindow = WinAPI_GetForegroundWindow() * Get location of the mouse pointer. DECLARE INTEGER GetCursorPos IN user32.dll AS WinAPI_GetCursorPos STRING @ lcPOINT = REPLICATE(CHR(0), 8) lnReturn = WinAPI_GetCursorPos(@lcPOINT) lnXCoord = THIS.StringToInteger(LEFT(m.lcPOINT,4)) lnYCoord = THIS.StringToInteger(SUBSTR(m.lcPOINT, 5)) * Get the position and coordinates of the taskbar. lnTaskBarLeft = 0 lnTaskBarRight = 0 lnTaskBarTop = 0 lnTaskBarBottom = 0 lnTaskBarLocation = THIS.GetTaskBarPosition(@lnTaskBarLeft, @lnTaskBarTop, @lnTaskBarRight, @lnTaskBarBottom) * Need Top-Level form. * It also has ShowInTaskBar=.F. so we don't get a large button on the taskbar. * It is also invisible, using the SetWindowLong API function in the form's INIT. lcClassLib = "systray.vcx" llContinue = .T. DO WHILE llContinue TRY loTempForm = NEWOBJECT("x_frmInternalSystrayUseOnly", lcClassLib ) llContinue = .F. CATCH IF lcClassLib = "systray.vcx" * Try to create the form by looking in the same place as this object lcClassLib = ADDBS(JUSTPATH(SYS(1271, this))) + "systray.vcx" ELSE loTempForm = .Null. llContinue = .F. ENDIF ENDTRY ENDDO * Now position form near mouse, but adjust for taskbar. DO CASE CASE m.lnTaskBarLocation = 1 && Left loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarRight + 7 CASE m.lnTaskBarLocation = 2 && Top * Ideally would determine height of titlebar, and subtract that. * But this is sufficient. loTempForm.top = m.lnTaskBarBottom - 8 loTempForm.Left = m.lnXCoord CASE m.lnTaskBarLocation = 3 && Right loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarLeft - this.MenuOffsetFromRight && Roughly the width of a typical menu. OTHERWISE && Bottom, or coding error. * Just place the window near the mouse. loTempForm.top = m.lnYCoord loTempForm.Left = m.lnXCoord ENDCASE * Window must be the foreground window when shortcut menu displayed. Otherwise, * clicking in another window won't release the shortcut menu. loTempForm.Show THIS.SetForegroundWindow(loTempForm.hWnd) * Now display the menu. DO CASE CASE (PCOUNT() > 0 AND TYPE("m.lcMPRFileName") = "C") * Want to run the MPR specified in parameter IF PCOUNT() = 1 DO (m.lcMPRFileName) RELEASE loTempForm ELSE * Build parameter list LOCAL lcParamList, lnCount lcParamList = "p1" FOR nCount = 3 TO PCOUNT() && if PCOUNT() is only 2, don't enter loop. lcParamList = m.lcParamList + ", p" + TRANSFORM(m.lnCount - 1) ENDFOR DO (m.lcMPRFileName) WITH &lcParamList RELEASE loTempForm ENDIF CASE THIS.MenuTextIsMPR AND FILE(THIS.MenuText) * We want to run the MPR specified in MenuText property. DO (THIS.MenuText) RELEASE loTempForm OTHERWISE * Build menu from this.MenuText. LOCAL lnMenuItems, lcMenuName, laMenuItems[1], lnCurrentMenuItem LOCAL lnToken, lcText, lnBarNum lnMenuItems = ALINES(m.laMenuItems, THIS.MenuText, .T., ";") IF m.lnMenuItems = 0 RELEASE loTempForm RETURN .f. ENDIF IF m.lnMenuItems % 2 != 0 * Incorrect number of items in MenuText property. IF m.lnMenuItems > 1 lnMenuItems = lnMenuItems - 1 ELSE RELEASE loTempForm RETURN .f. ENDIF ENDIF lcMenuName = SYS(2015) * Make private variable to receive the ID of the selected menu item. PRIVATE __systray_menu_return __systray_menu_return = 0 * Build the menu. DEFINE POPUP (m.lcMenuName) SHORTCUT RELATIVE FROM MROW(),MCOL() FOR lnCurrentMenuItem = 1 TO m.lnMenuItems STEP 2 lnToken = VAL(m.laMenuItems[m.lnCurrentMenuItem]) lcText = m.laMenuItems[m.lnCurrentMenuItem + 1] lnBarNum = FLOOR(m.lnCurrentMenuItem/2) + 1 DEFINE BAR (lnBarNum) OF (m.lcMenuName) PROMPT (m.lcText) lcOnSelectionBar = "ON SELECTION BAR " + TRANSFORM(m.lnBarNum) + " OF " + ; TRANSFORM(m.lcMenuName) + " __systray_menu_return = " + TRANSFORM(m.lnToken) &lcOnSelectionBar ENDFOR SET ESCAPE off * Display the menu and wait for user action. ACTIVATE POPUP (m.lcMenuName) RELEASE loTempForm THIS.ProcessMenuEvent(__systray_menu_return) RETURN __systray_menu_return ENDCASE * Menu has been deactivated. Restore previous foreground window. This.SetForegroundWindow(m.lhPreviousWindow) ENDPROC PROCEDURE processmenuevent LPARAMETERS nMenuItemID * This event is fired after a menu has been clicked or cleared. ENDPROC PROCEDURE iconclickevent * This "event" is called when the user left-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE integertostring LPARAMETERS nInteger * Changes an integer into the Intel representation of that integer. * Used to build structures. RETURN CHR(BITAND(nInteger, 255)) + CHR(BITAND(BITRSHIFT(nInteger, 8), 255)) + ; CHR(BITAND(BITRSHIFT(nInteger, 16), 255)) + CHR(BITAND(BITRSHIFT(nInteger, 24), 255)) ENDPROC PROCEDURE loadicon LPARAMETERS cIconFileName * Loads an icon as a Windows resource. Returns the resource handle. LOCAL cOldTalk, cTempFileName, hImage DECLARE INTEGER LoadImage IN user32.dll AS WinAPI_LoadImage ; INTEGER hinst, ; STRING lpszName, ; INTEGER uType, ; && uint INTEGER cxDesired, ; INTEGER cyDesired, ; INTEGER fuLoad && uint ** LoadImage defines **** #define IMAGE_ICON 1 #define LR_LOADFROMFILE 0x0010 * Assume that the icon is contained in the app/exe file. Copy to temp before loading. IF FILE(cIconFileName) cTempFileName = ADDBS(GETENV("temp")) + "systray_temp_" + SYS(3) + ".icn" COPY FILE (cIconFileName) TO (cTempFileName) ELSE RETURN ENDIF m.hImage = WinAPI_LoadImage( 0, ; && Don't load from resource file cTempFileName, ; IMAGE_ICON, ; 0, ; 0, ; LR_LOADFROMFILE) * Now need to erase the temp file. SET TALK OFF to avoid the "File has been erased" * message on the status bar. cOldTalk = SET("TALK") ERASE (cTempFileName) SET TALK &cOldTalk IF m.hImage = 0 this.Error(0, "Failed to load icon '" + cIconFileName + "'. in " + PROGRAM(), LINENO()) ENDIF RETURN m.hImage ENDPROC PROCEDURE tiptext_assign LPARAMETERS vNewVal * The text for the tooltip is being changed. THIS.tiptext = ALLTRIM(m.vNewVal) IF THIS.Enabled * This icon is already displayed in the Taskbar Notification Area, * so we must update the systray immediately: this.AddIconToSystray() endif ENDPROC PROCEDURE currenticonindex_assign LPARAMETERS vNewVal * This is called when user changes the CurrentIconIndex property. THIS.currenticonindex = m.vNewVal IF this.Enabled * We're attached to systray. Update immediately. this.AddIconToSystray ENDIF ENDPROC PROCEDURE stringtointeger LPARAMETERS lcPDWORD, lnBytes * Converts a string from the Intel int, DWORD,WORD, and BYTE formats * into a VFP integer. IF PCOUNT() < 2 lnBytes = 4 && No length provided, assume 4-byte integer. ENDIF LOCAL lnReturn, lnCurByte lnReturn = 0 FOR lnCurByte = 1 to m.lnBytes lnReturn = m.lnReturn + ASC(SUBSTR(m.lcPDWord, m.lnCurByte, 1))*(256^(m.lnCurByte-1)) ENDFOR RETURN lnReturn ENDPROC PROCEDURE gettaskbarposition LPARAMETERS nLeft, nTop, nRight, nBottom * We position any menus near where the mouse just clicked. But we need to * fine-tune that position depending on which edge of the screen the * user has docked the taskbar. This method retrieves the absolute * coordinates of the taskbar, and derives from those coordinates which * edge the taskbar is on. #DEFINE ABM_GETTASKBARPOS 0x00000005 DECLARE INTEGER SHAppBarMessage IN shell32.dll as WinAPI_SHAppBarMessage ; INTEGER dwMessage, STRING @ PAPPBARDATA LOCAL cAPPBARDATA, nReturn WITH THIS cAPPBARDATA = .IntegerToString(0) && hWnd cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && uCallBackMsg cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && Edge cAPPBARDATA = cAPPBARDATA + REPLICATE(CHR(0), 4*4) && RECT structure cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && lParam cAPPBARDATA = .IntegerToString(LEN(cAPPBARDATA) + 4) + cAPPBARDATA nReturn = WinAPI_SHAppBarMessage(ABM_GETTASKBARPOS, @cAPPBARDATA) * Taskbar coordinates should be in RECT structure. nLeft = .StringToInteger(SUBSTR(cAPPBARDATA, 17, 4)) IF nLeft > 0xFFFFFF && If the number is ridiculously large, assume it was a negative number. nLeft = 0 - (0xFFFFFFFF - nLeft) ENDIF nTop = .StringToInteger(SUBSTR(cAPPBARDATA, 21, 4)) IF nTop > 0xFFFFFF nTop = 0 - (0xFFFFFFFF - nTop) ENDIF nRight = .StringToInteger(SUBSTR(cAPPBARDATA, 25, 4)) IF nRight > 0xFFFFFF nRight = 0 - (0xFFFFFFFF - nRight) ENDIF nBottom = .StringToInteger(SUBSTR(cAPPBARDATA, 29, 4)) IF nBottom > 0xFFFFFF nBottom = 0 - (0xFFFFFFFF - nBottom) ENDIF ENDWITH DO CASE * Must use approximations for taskbar coordinates, because they oare slightly different * on different versions of Windows. CASE nLeft < 2 AND nTop < 2 AND nRight < 479 && Roughly minimum width in portrait mode. RETURN 1 && Left CASE nLeft < 2 AND nTop > 0 RETURN 0 && Bottom CASE nLeft < 2 AND nTop < 2 && Must be top, otherwise first case would take it. RETURN 2 && Top OTHERWISE RETURN 3 && Right ENDCASE *!* typedef struct _AppBarData { *!* DWORD cbSize; *!* HWND hWnd; *!* UINT uCallbackMessage; *!* UINT uEdge; *!* RECT rc; *!* LPARAM lParam; *!* } APPBARDATA, *PAPPBARDATA *!* typedef struct _RECT { *!* LONG left; *!* LONG top; *!* LONG right; *!* LONG bottom; *!* } RECT ENDPROC PROCEDURE getshellversion DECLARE INTEGER DllGetVersion IN shell32.dll AS WinAPI_Shell32_DllGetVersion ; STRING @ DLLVERSIONINFO * Windows 98 typically returns 4.72, depending on version of IE installed. * Windows ME returns 5.5 or greater. * Windows 2000 returns 5.0 * Windows XP returns 6.0 LOCAL lcDLLVersionInfo, lnMajor, lnMinor WITH THIS lcDLLVersionInfo = .IntegerToString(20) lcDLLVersionInfo = m.lcDLLVersionInfo + REPLICATE(CHR(0), 16) WinAPI_Shell32_DllGetVersion(@lcDLLVersionInfo) lnMajor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 5, 4)) lnMinor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 9, 4)) RETURN VAL(STR(m.lnMajor, 2, 0) + "." + ALLTRIM(STR(m.lnMinor,3,0))) ENDWITH ENDPROC PROCEDURE iconrightclickevent * This "event" is called when the user right-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE iconfile_assign LPARAMETERS vNewVal *To do: Modify this routine for the Assign method THIS.ICONFILE = m.vNewVal * Updating this property will release all other icons. THIS.ClearIconList() THIS.AddIconToIconList(m.vNewVal) && Going from 0 to 1 icon updates systray immediately. ENDPROC PROCEDURE showballoontip LPARAMETERS lcBalloonText, lcBalloonTitle, lnIcon, lnTimeout * Displays a balloon tip if the Operating System shell supports it. * The lnTimeout parameter is the number of SECONDS to display the balloon. This * timeout period is subject to minimum and maximum values set by the operating * system (typically 10 and 30 seconds). * The lnIcon parameter specifies the icon to display in the * balloon. The values are as follows: * 0 = No icon * 1 = Info. A lowercase "i" in a small balloon * 2 = Warning. An exclamation point (!) in a triangle. * 3 = Error. A red disk with an X through it. * Add 16 (0x10) to the value to mute the sound that is played when * the balloon is displayed (Windows XP and later). * This method uses #DEFINES that are set in the AddIconToSystray method. IF PCOUNT() < 1 * Must at least include the balloon Text. RETURN -1 ENDIF IF THIS.ShellVersion < 5 &&GetShellVersion() < 5.00 * This shell doesn't support balloon tips. RETURN -1 ENDIF IF TYPE("m.lcBalloonText") <> "C" RETURN -1 ENDIF IF TYPE("m.lcBalloonTitle") <> "C" lcBalloonTitle = "" ENDIF IF TYPE("m.lnIcon") <> "N" lnIcon = 0 ENDIF IF TYPE("m.lnTimeout") <> "N" lnTimeout = 0 ELSE lnTimeOut = m.lnTimeout * 1000 && Convert seconds to Milliseconds. ENDIF LOCAL lcNotifyIconData, lnReturn WITH THIS * Build NOTIFYICONDATA structure compatible with version 5 of the shell. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON, NIF_INFO)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonText), 255),256, CHR(0)) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(m.lnTimeOut) && Timeout/Version lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonTitle), 63),64, CHR(0)) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(lnIcon, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * Send request to display balloon. This is asynchronous, so * balloon may not be displayed immediately. lnReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) ENDWITH ENDPROC PROCEDURE Init * If there is an initial icon specified, add it to the Icon List. IF TYPE("THIS.IconFile") = "C" AND LEN(THIS.IconFile) > 0 THIS.AddIconToIconList(THIS.IconFile) ENDIF * Each SystemTray icon in this process requires a unique ID. We'll * use an _screen property to make sure each instance of this class * gets its own ID. Properties on _screen aren't affected by CLEAR ALL. * Because VFP drops the least significant two bytes of the mousewheel event's * WPARAM parameter, our unique SysTray Icon ID must use the most significant * two bytes: #DEFINE MIN_SYSTRAY_ICON_ID 0x4000 IF TYPE("_screen.nSysTrayCount") == "U" * We're the first one here. _screen.AddProperty("nSysTrayCount", 0) ELSE IF _Screen.nSysTrayCount > 0x3FFF * User is apparently creating and destroying this object repeatedly. _Screen.nSysTrayCount = 0 ENDIF ENDIF _screen.nSysTrayCount = _screen.nSysTrayCount + 1 this.SystrayIconID = _screen.nSysTrayCount + MIN_SYSTRAY_ICON_ID * By default, VFP combines MouseWheel events. (That is, if we receive * a MouseWheel event from the OS, we check our internal queue to see * if there is already a MouseWheel event that hasn't been processed yet. * If found, we just add the number of MouseWheel turns to the existing * event.) This behavior makes our communication with the SystemTray * unstable, so we must disable this combining of events. SYS(2060) * accomplishes this: THIS.nPrevMWAccrual = VAL(SYS(2060)) SYS(2060, 1) * Add icon to systray if requested. IF this.AddIconToSystrayAtInit this.AddIconToSystray ENDIF ENDPROC PROCEDURE Destroy WITH THIS IF .Enabled .RemoveIconFromSystray() .ClearIconList() && Release resources. ENDIF IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, .nPrevMWAccrual) && Reset mousewheel behavior. ENDIF ENDWITH ENDPROC Height = 23 Width = 23 tiptext = menutext = iconidentifier = 0 iconcount = 0 systrayiconid = 0 currenticonindex = 0 iconfile = menuoffsetfromright = 200 addicontosystrayatinit = .T. shellversion = 0 Name = "systray"  hyperlink hyperlink hyperlinksystrayHeight = 23 Width = 23 tiptext = menutext = iconidentifier = 0 iconcount = 0 systrayiconid = 0 currenticonindex = 0 iconfile = menuoffsetfromright = 200 addicontosystrayatinit = .T. shellversion = 0 Name = "systray" }hPROCEDURE receiveiconevent LPARAMETERS nDirection, nShift, nXCoord, nYCoord * This is the procedure that receives the Notify Icon events. Since * we execute the BindEvent() function from within this class, this * procedure can be hidden. * nDirection contains our icon identifier. IF nDirection != this.SystrayIconID * Not our icon, or this a real MouseWheel event on the VFP window. RETURN .F. ENDIF * These are the events that we receive from the Taskbar icon. #DEFINE WM_MOUSEMOVE 0x0200 #DEFINE WM_LBUTTONDOWN 0x0201 #DEFINE WM_LBUTTONUP 0x0202 #DEFINE WM_LBUTTONDBLCLK 0x0203 #DEFINE WM_RBUTTONDOWN 0x0204 #DEFINE WM_RBUTTONUP 0x0205 #DEFINE WM_RBUTTONDBLCLK 0x0206 #DEFINE WM_MBUTTONDOWN 0x0207 #DEFINE WM_MBUTTONUP 0x0208 #DEFINE WM_MBUTTONDBLCLK 0x0209 * Mousewheel events also get passed, but are difficult to decipher. #define WM_CONTEXTMENU 0x007B && Same as RightClick, but used when && Version 5 events have been specified. && (See the DisplayBalloonTip method.) #define WM_USER 0x0400 #define NIN_SELECT WM_USER + 0 #define NINF_KEY 0x1 #define NIN_KEYSELECT BITOR(NIN_SELECT , NINF_KEY) * Balloon events supported on Windows ME and Windows XP, and later. Not supported on Win2k. #DEFINE NIN_BALLOONSHOW (WM_USER + 2) #DEFINE NIN_BALLOONHIDE (WM_USER + 3) #DEFINE NIN_BALLOONTIMEOUT (WM_USER + 4) #DEFINE NIN_BALLOONUSERCLICK (WM_USER + 5) * Even though this isn't a real MouseWheel event on the VFP window, * VFP gives us the coordinates with respect to the location of the * main VFP window. Must change back to global coordinates. nXCoord = nXCoord + _screen.Left IF nXCoord <> WM_MOUSEMOVE && Ignore mousemove events. * The Version 5 events are more difficult to deal with, * but Version 5 supports Balloon Tips. So the extra work * is worth it. IF THIS.ShellVersion >= 5 DO CASE CASE nXCoord = NIN_SELECT OR nXCoord = NIN_KEYSELECT THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_CONTEXTMENU && Shell version 5 and later THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN CASE nXCoord = NIN_BALLOONSHOW THIS.BalloonShowEvent RETURN CASE nXCoord = NIN_BALLOONHIDE THIS.BalloonHideEvent RETURN CASE nXCoord = NIN_BALLOONTIMEOUT THIS.BalloonTimeoutEvent RETURN CASE nXCoord = NIN_BALLOONUSERCLICK This.BalloonClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ELSE * Using Version 4 events. DO CASE CASE nXCoord = WM_LBUTTONDOWN THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_RBUTTONDOWN THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ENDIF ENDIF ENDPROC PROCEDURE setforegroundwindow LPARAMETERS lhWnd * Brings the specified top-level window to the top of the Windows z-order. * If no parameter specified, then THISFORM is made the foreground window. IF TYPE("m.lhWnd") = "N" DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(m.lhWnd) RETURN ENDIF RETURN * If we're here, then the user called this method without an * hWnd parameter. Assume it is thisform that is to be brought forward. IF VARTYPE(thisform) == "O" IF ! thisform.Visible thisform.Visible = .t. ENDIF * First make sure it isn't minimized. IF thisform.WindowState= 1 thisform.WindowState = 0 ENDIF * If necessary, make sure the main VFP window is not minimized. IF _screen.Visible AND thisform.ShowWindow = 0 IF _screen.WindowState = 1 _screen.WindowState = 0 && Set back to normal ENDIF ENDIF DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(thisform.HWnd) ENDIF ENDPROC PROCEDURE cleariconlist * Removes all icons from the icon list. * In detail: * Releases the resource handles of all icons that have been loaded, * clears the array and sets the count to zero. This method is called * by the Destroy event, to make sure all system resources are correctly * released. WITH this IF .IconCount = 0 * No icons in list RETURN ENDIF * Have to release all icon resources DECLARE INTEGER DestroyIcon IN user32.dll AS WinAPI_DestroyIcon ; INTEGER hIcon FOR nCurrentIcon = 1 TO ALEN(.aIconList,1) WinAPI_DestroyIcon(.aIconList[m.nCurrentIcon]) ENDFOR DIMENSION .aIconList[1] .IconCount = 0 .CurrentIconIndex = 0 ENDWITH ENDPROC PROCEDURE addicontoiconlist LPARAMETERS cIconFileName IF !FILE(cIconFileName) RETURN .f. ENDIF * We must load the icon as a Windows resource. nHandle = THIS.LoadIcon(m.cIconFileName) IF m.nHandle = 0 RETURN .f. ENDIF * We have a resource handle for the icon. Now we store that * handle (integer) in our array of icon resource handles. WITH THIS .IconCount = .IconCount + 1 DIMENSION .aIconList[.IconCount] .aIconList[.IconCount] = m.nHandle IF .CurrentIconIndex = 0 .CurrentIconIndex = 1 && Updates Systray immediately ENDIF ENDWITH RETURN .T. ENDPROC PROCEDURE switchicon * Switches to the next icon in the iconlist. * Typically called by a timer to regularly change the icon, * to create animation. WITH THIS IF .IconCount > 1 IF .CurrentIconIndex = .iconCount .CurrentIconIndex = 1 ELSE .CurrentIconIndex = .CurrentIconIndex + 1 ENDIF ENDIF RETURN .CurrentIconIndex ENDWITH ENDPROC PROCEDURE addicontosystray *!* This is where the magic happens. *!* This is an unabashed hack to allow us to get the full functionality *!* of the Taskbar Notification Area ("System Tray") without having to *!* use an external C++ library. *!* To communicate with the systray, we tell it to send us messages via *!* the MouseWheel event. (This is the only VFP event that doesn't alter *!* or discard event data before firing the corresponding internal event.) *!* The second trick is that only the main VFP window will accept the events *!* without checking to see if the event coordinates are invalid. (The screen *!* doesn't even need to be visible, so you can have SCREEN=OFF in your *!* config.fpw file.) So we use VFP8's BINDEVENT() function to bind to the *!* _SCREEN.MouseWheel event. *!* This method sets up that communication path. *!* Returns 1 if successful. LOCAL lcNotifyIconData ** NOTIFYICON struct defines #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 && Win2k and later. #define NIF_INFO 0x00000010 && Use balloon tip. Win2k and later. && Notify Icon Infotip flags #define NIIF_NONE 0x00000000 && icon flags are mutually exclusive && and take only the lowest 2 bits #define NIIF_INFO 0x00000001 #define NIIF_WARNING 0x00000002 #define NIIF_ERROR 0x00000003 #define NIIF_ICON_MASK 0x0000000F #define NIIF_NOSOUND 0x00000010 && Windows XP and later. #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIM_SETFOCUS 0x00000003 && Windows 2000 and later. #define NIM_SETVERSION 0x00000004 && Windows 2000 and later. #define NOTIFYICON_VERSION 3 #define NIS_HIDDEN 0x00000001 #define NIS_SHAREDICON 0x00000002 WITH THIS * If no icons loaded, do nothing. IF .IconCount < 1 OR .CurrentIconIndex = 0 RETURN 0 ENDIF IF NOT .Enabled * Each SystemTray icon in this process requires a unique ID. We'll * use an _screen property to make sure each instance of this class * gets its own ID. Properties on _screen aren't affected by CLEAR ALL. * Because VFP drops the least significant two bytes of the mousewheel event's * WPARAM parameter, our unique SysTray Icon ID must use the most significant * two bytes: #DEFINE MIN_SYSTRAY_ICON_ID 0x4000 IF TYPE("_screen.nSysTrayCount") == "U" * We're the first one here. _screen.AddProperty("nSysTrayCount", 0) ELSE IF _Screen.nSysTrayCount > 0x3FFF * User is apparently creating and destroying this object repeatedly. _Screen.nSysTrayCount = 0 ENDIF ENDIF _screen.nSysTrayCount = _screen.nSysTrayCount + 1 this.SystrayIconID = _screen.nSysTrayCount + MIN_SYSTRAY_ICON_ID * By default, VFP combines MouseWheel events. (That is, if we receive * a MouseWheel event from the OS, we check our internal queue to see * if there is already a MouseWheel event that hasn't been processed yet. * If found, we just add the number of MouseWheel turns to the existing * event.) This behavior makes our communication with the SystemTray * unstable, so we must disable this combining of events. SYS(2060) * accomplishes this: THIS.nPrevMWAccrual = VAL(SYS(2060)) SYS(2060, 1) ENDIF * Declare the WinAPI function that lets us install the icon. * We redeclare the function every time, just in case CLEAR DLLS is called * elsewhere in the app. DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; STRING @ PNOTIFYICONDATA IF THIS.ShellVersion = 0 && If we haven't set the version yet. nTipTextMaxLength = 63 && Default to version 4. ELSE nTipTextMaxLength = 127 ENDIF * Build NOTIFYICONDATA structure. lcNotifyIconData = .IntegerToString(_VFP.Hwnd) && Messages get sent to VFP's main window. lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), nTipTextMaxLength ), nTipTextMaxLength + 1, CHR(0)) && TipText IF .ShellVersion >= 5 lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags ENDIF lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure IF NOT .Enabled && If not already in Taskbar Notification Area.... * Add icon to Taskbar Notification Area. nReturn = WinAPI_Shell_NotifyIcon( NIM_ADD, @lcNotifyIconData) IF nReturn <> 1 * Adding item to tray failed!!! IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, .nPrevMWAccrual) && Reset mousewheel behavior. ENDIF RETURN nReturn ENDIF * Bind to the MouseWheel event on VFP's main window. nReturn = BINDEVENT(_screen,"MouseWheel",this,"receiveIconEvent",2) .Enabled = .t. * We've just added the icon. Now we see if we can set to Version 5 shell behavior, * with larger tooltip lengths and support for balloon Tips. IF .GetShellVersion() >= 5 * This OS supports version 5 features. Switch to version 5. * We have to inform the Shell that we want to use Version 5 features. * Build a version 5 structure for sending message to change version. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * First we check to see if we can switch to using version 5 events. nReturn = WinAPI_Shell_NotifyIcon( NIM_SETVERSION, @lcNotifyIconData) IF nReturn <> 1 * Couldn't switch to version 5 events RETURN -1 ENDIF .ShellVersion = 5 * Now that we've updated to Version 5, we have to modify the existing icon * if the .TipText is longer than 63 characters. IF LEN(.TipText) > 63 nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 RETURN nReturn ENDIF ENDIF ELSE .ShellVersion = 4 ENDIF ELSE * Already in the Taskbar Notification Area. Just update existing icon. nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 MESSAGEBOX(" * Modifying item in tray failed!!!") RETURN nReturn ENDIF ENDIF ENDWITH RETURN nReturn ENDPROC PROCEDURE removeiconfromsystray * Removes the icon from the Taskbar Notification Area ('System Tray'). DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; string @ PNOTIFYICONDATA #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 #define NIF_INFO 0x00000010 LOCAL cNotifyIconData WITH THIS UNBINDEVENTS(_screen, "MouseWheel", this, "receiveIconEvent") .Enabled = .f. * Build structure. cNotifyIconData = .IntegerToString(_vfp.hwnd) cNotifyIconData = cNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) cNotifyIconData = cNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE, ,NIF_TIP cNotifyIconData = cNotifyIconData + .IntegerToString(0x20A) && uCallback cNotifyIconData = cNotifyIconData + .IntegerToString(0) && icon handle cNotifyIconData = cNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 63), 64, CHR(0)) cNotifyIconData = .integertostring(LEN(cNotifyIconData) + 4) + cNotifyIconData ENDWITH nReturn = WinAPI_Shell_NotifyIcon(NIM_DELETE, @cNotifyIconData) THIS.ShellVersion = 0 IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, .nPrevMWAccrual) && Reset mousewheel behavior. ENDIF ENDPROC PROCEDURE showmenu LPARAMETERS lcMPRFileName, p1, p2, p3, p4, p5, p6 * Displays the shortcut menu near the taskbar icon. * If the menu is defined as a set of tokens and text strings, the * menu is defined here. Otherwise, the specified MPR is called. * If the first parameter is blank or not specified, the contents * of the MenuText property are used to define/determine the menu. * If the first parameter is not blank, it is assumed to specify * the MPR to run. The MenuText property is ignored. * * The additional parameters are passed on to the MPR. They are only * used if the cMPRFileName parameter is specified and non-blank. * * In order to display the shortcut menu in the proper location, * we must activate it within a toplevel form (with .visible = .t.), * near the taskbar. * On WindowsME, Win2k and later, the form is made invisible by * calling a Windows API function. (See the INIT of the form class.) * But on Win98 and NT4, that API function didn't exist, so we make it * a "Desktop" form instead. This allows us to remove the titlebar and * make the form so small it won't be noticed. But it causes slightly * incorrect behavior when trying to clear the menu. LOCAL lhPreviousWindow, lcPOINT, lnReturn, lnXCoord, lnYCoord, lcClassLib, llContinue LOCAL lnTaskBarLeft, lnTaskBarTop, lnTaskBarRight, lnTaskBarBottom, lnTaskBarLocation LOCAL loTempForm as Form * Our invisible form must be made the top window in order to make the shortcut * menu get cleared properly if user clicks elsewhere. Therefore, we store the * handle of the current foreground window, so we can restore it later. DECLARE INTEGER GetForegroundWindow IN user32.dll AS WinAPI_GetForegroundWindow lhPreviousWindow = WinAPI_GetForegroundWindow() * Get location of the mouse pointer. DECLARE INTEGER GetCursorPos IN user32.dll AS WinAPI_GetCursorPos STRING @ lcPOINT = REPLICATE(CHR(0), 8) lnReturn = WinAPI_GetCursorPos(@lcPOINT) lnXCoord = THIS.StringToInteger(LEFT(m.lcPOINT,4)) lnYCoord = THIS.StringToInteger(SUBSTR(m.lcPOINT, 5)) * Get the position and coordinates of the taskbar. lnTaskBarLeft = 0 lnTaskBarRight = 0 lnTaskBarTop = 0 lnTaskBarBottom = 0 lnTaskBarLocation = THIS.GetTaskBarPosition(@lnTaskBarLeft, @lnTaskBarTop, @lnTaskBarRight, @lnTaskBarBottom) * Need Top-Level form. * It also has ShowInTaskBar=.F. so we don't get a large button on the taskbar. * It is also invisible, using the SetWindowLong API function in the form's INIT. lcClassLib = "systray.vcx" llContinue = .T. DO WHILE llContinue TRY loTempForm = NEWOBJECT("x_frmInternalSystrayUseOnly", lcClassLib ) llContinue = .F. CATCH IF lcClassLib = "systray.vcx" * Try to create the form by looking in the same place as this object lcClassLib = ADDBS(JUSTPATH(SYS(1271, this))) + "systray.vcx" ELSE loTempForm = .Null. llContinue = .F. ENDIF ENDTRY ENDDO * Now position form near mouse, but adjust for taskbar. DO CASE CASE m.lnTaskBarLocation = 1 && Left loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarRight + 7 CASE m.lnTaskBarLocation = 2 && Top * Ideally would determine height of titlebar, and subtract that. * But this is sufficient. loTempForm.top = m.lnTaskBarBottom - 8 loTempForm.Left = m.lnXCoord CASE m.lnTaskBarLocation = 3 && Right loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarLeft - this.MenuOffsetFromRight && Roughly the width of a typical menu. OTHERWISE && Bottom, or coding error. * Just place the window near the mouse. loTempForm.top = m.lnYCoord loTempForm.Left = m.lnXCoord ENDCASE * Window must be the foreground window when shortcut menu displayed. Otherwise, * clicking in another window won't release the shortcut menu. loTempForm.Show THIS.SetForegroundWindow(loTempForm.hWnd) * Now display the menu. DO CASE CASE (PCOUNT() > 0 AND TYPE("m.lcMPRFileName") = "C") * Want to run the MPR specified in parameter IF PCOUNT() = 1 DO (m.lcMPRFileName) RELEASE loTempForm ELSE * Build parameter list LOCAL lcParamList, lnCount lcParamList = "p1" FOR nCount = 3 TO PCOUNT() && if PCOUNT() is only 2, don't enter loop. lcParamList = m.lcParamList + ", p" + TRANSFORM(m.lnCount - 1) ENDFOR DO (m.lcMPRFileName) WITH &lcParamList RELEASE loTempForm ENDIF CASE THIS.MenuTextIsMPR AND FILE(THIS.MenuText) * We want to run the MPR specified in MenuText property. DO (THIS.MenuText) RELEASE loTempForm OTHERWISE * Build menu from this.MenuText. LOCAL lnMenuItems, lcMenuName, laMenuItems[1], lnCurrentMenuItem LOCAL lnToken, lcText, lnBarNum lnMenuItems = ALINES(m.laMenuItems, THIS.MenuText, .T., ";") IF m.lnMenuItems = 0 RELEASE loTempForm RETURN .f. ENDIF IF m.lnMenuItems % 2 != 0 * Incorrect number of items in MenuText property. IF m.lnMenuItems > 1 lnMenuItems = lnMenuItems - 1 ELSE RELEASE loTempForm RETURN .f. ENDIF ENDIF lcMenuName = SYS(2015) * Make private variable to receive the ID of the selected menu item. PRIVATE __systray_menu_return __systray_menu_return = 0 * Build the menu. DEFINE POPUP (m.lcMenuName) SHORTCUT RELATIVE FROM MROW(),MCOL() FOR lnCurrentMenuItem = 1 TO m.lnMenuItems STEP 2 lnToken = VAL(m.laMenuItems[m.lnCurrentMenuItem]) lcText = m.laMenuItems[m.lnCurrentMenuItem + 1] lnBarNum = FLOOR(m.lnCurrentMenuItem/2) + 1 DEFINE BAR (lnBarNum) OF (m.lcMenuName) PROMPT (m.lcText) lcOnSelectionBar = "ON SELECTION BAR " + TRANSFORM(m.lnBarNum) + " OF " + ; TRANSFORM(m.lcMenuName) + " __systray_menu_return = " + TRANSFORM(m.lnToken) &lcOnSelectionBar ENDFOR SET ESCAPE off * Display the menu and wait for user action. ACTIVATE POPUP (m.lcMenuName) RELEASE loTempForm THIS.ProcessMenuEvent(__systray_menu_return) RETURN __systray_menu_return ENDCASE * Menu has been deactivated. Restore previous foreground window. This.SetForegroundWindow(m.lhPreviousWindow) ENDPROC PROCEDURE processmenuevent LPARAMETERS nMenuItemID * This event is fired after a menu has been clicked or cleared. ENDPROC PROCEDURE iconclickevent * This "event" is called when the user left-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE integertostring LPARAMETERS nInteger * Changes an integer into the Intel representation of that integer. * Used to build structures. RETURN CHR(BITAND(nInteger, 255)) + CHR(BITAND(BITRSHIFT(nInteger, 8), 255)) + ; CHR(BITAND(BITRSHIFT(nInteger, 16), 255)) + CHR(BITAND(BITRSHIFT(nInteger, 24), 255)) ENDPROC PROCEDURE loadicon LPARAMETERS cIconFileName * Loads an icon as a Windows resource. Returns the resource handle. LOCAL cOldTalk, cTempFileName, hImage DECLARE INTEGER LoadImage IN user32.dll AS WinAPI_LoadImage ; INTEGER hinst, ; STRING lpszName, ; INTEGER uType, ; && uint INTEGER cxDesired, ; INTEGER cyDesired, ; INTEGER fuLoad && uint ** LoadImage defines **** #define IMAGE_ICON 1 #define LR_LOADFROMFILE 0x0010 * Assume that the icon is contained in the app/exe file. Copy to temp before loading. IF FILE(cIconFileName) cTempFileName = ADDBS(GETENV("temp")) + "systray_temp_" + SYS(3) + ".icn" COPY FILE (cIconFileName) TO (cTempFileName) ELSE RETURN ENDIF m.hImage = WinAPI_LoadImage( 0, ; && Don't load from resource file cTempFileName, ; IMAGE_ICON, ; 0, ; 0, ; LR_LOADFROMFILE) * Now need to erase the temp file. SET TALK OFF to avoid the "File has been erased" * message on the status bar. cOldTalk = SET("TALK") ERASE (cTempFileName) SET TALK &cOldTalk IF m.hImage = 0 this.Error(0, "Failed to load icon '" + cIconFileName + "'. in " + PROGRAM(), LINENO()) ENDIF RETURN m.hImage ENDPROC PROCEDURE tiptext_assign LPARAMETERS vNewVal * The text for the tooltip is being changed. THIS.tiptext = ALLTRIM(m.vNewVal) IF THIS.Enabled * This icon is already displayed in the Taskbar Notification Area, * so we must update the systray immediately: this.AddIconToSystray() endif ENDPROC PROCEDURE currenticonindex_assign LPARAMETERS vNewVal * This is called when user changes the CurrentIconIndex property. THIS.currenticonindex = m.vNewVal IF this.Enabled * We're attached to systray. Update immediately. this.AddIconToSystray ENDIF ENDPROC PROCEDURE stringtointeger LPARAMETERS lcPDWORD, lnBytes * Converts a string from the Intel int, DWORD,WORD, and BYTE formats * into a VFP integer. IF PCOUNT() < 2 lnBytes = 4 && No length provided, assume 4-byte integer. ENDIF LOCAL lnReturn, lnCurByte lnReturn = 0 FOR lnCurByte = 1 to m.lnBytes lnReturn = m.lnReturn + ASC(SUBSTR(m.lcPDWord, m.lnCurByte, 1))*(256^(m.lnCurByte-1)) ENDFOR RETURN lnReturn ENDPROC PROCEDURE gettaskbarposition LPARAMETERS nLeft, nTop, nRight, nBottom * We position any menus near where the mouse just clicked. But we need to * fine-tune that position depending on which edge of the screen the * user has docked the taskbar. This method retrieves the absolute * coordinates of the taskbar, and derives from those coordinates which * edge the taskbar is on. #DEFINE ABM_GETTASKBARPOS 0x00000005 DECLARE INTEGER SHAppBarMessage IN shell32.dll as WinAPI_SHAppBarMessage ; INTEGER dwMessage, STRING @ PAPPBARDATA LOCAL cAPPBARDATA, nReturn WITH THIS cAPPBARDATA = .IntegerToString(0) && hWnd cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && uCallBackMsg cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && Edge cAPPBARDATA = cAPPBARDATA + REPLICATE(CHR(0), 4*4) && RECT structure cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && lParam cAPPBARDATA = .IntegerToString(LEN(cAPPBARDATA) + 4) + cAPPBARDATA nReturn = WinAPI_SHAppBarMessage(ABM_GETTASKBARPOS, @cAPPBARDATA) * Taskbar coordinates should be in RECT structure. nLeft = .StringToInteger(SUBSTR(cAPPBARDATA, 17, 4)) IF nLeft > 0xFFFFFF && If the number is ridiculously large, assume it was a negative number. nLeft = 0 - (0xFFFFFFFF - nLeft) ENDIF nTop = .StringToInteger(SUBSTR(cAPPBARDATA, 21, 4)) IF nTop > 0xFFFFFF nTop = 0 - (0xFFFFFFFF - nTop) ENDIF nRight = .StringToInteger(SUBSTR(cAPPBARDATA, 25, 4)) IF nRight > 0xFFFFFF nRight = 0 - (0xFFFFFFFF - nRight) ENDIF nBottom = .StringToInteger(SUBSTR(cAPPBARDATA, 29, 4)) IF nBottom > 0xFFFFFF nBottom = 0 - (0xFFFFFFFF - nBottom) ENDIF ENDWITH DO CASE * Must use approximations for taskbar coordinates, because they oare slightly different * on different versions of Windows. CASE nLeft < 2 AND nTop < 2 AND nRight < 479 && Roughly minimum width in portrait mode. RETURN 1 && Left CASE nLeft < 2 AND nTop > 0 RETURN 0 && Bottom CASE nLeft < 2 AND nTop < 2 && Must be top, otherwise first case would take it. RETURN 2 && Top OTHERWISE RETURN 3 && Right ENDCASE *!* typedef struct _AppBarData { *!* DWORD cbSize; *!* HWND hWnd; *!* UINT uCallbackMessage; *!* UINT uEdge; *!* RECT rc; *!* LPARAM lParam; *!* } APPBARDATA, *PAPPBARDATA *!* typedef struct _RECT { *!* LONG left; *!* LONG top; *!* LONG right; *!* LONG bottom; *!* } RECT ENDPROC PROCEDURE getshellversion DECLARE INTEGER DllGetVersion IN shell32.dll AS WinAPI_Shell32_DllGetVersion ; STRING @ DLLVERSIONINFO * Windows 98 typically returns 4.72, depending on version of IE installed. * Windows ME returns 5.5 or greater. * Windows 2000 returns 5.0 * Windows XP returns 6.0 LOCAL lcDLLVersionInfo, lnMajor, lnMinor WITH THIS lcDLLVersionInfo = .IntegerToString(20) lcDLLVersionInfo = m.lcDLLVersionInfo + REPLICATE(CHR(0), 16) WinAPI_Shell32_DllGetVersion(@lcDLLVersionInfo) lnMajor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 5, 4)) lnMinor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 9, 4)) RETURN VAL(STR(m.lnMajor, 2, 0) + "." + ALLTRIM(STR(m.lnMinor,3,0))) ENDWITH ENDPROC PROCEDURE iconrightclickevent * This "event" is called when the user right-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE iconfile_assign LPARAMETERS vNewVal *To do: Modify this routine for the Assign method THIS.ICONFILE = m.vNewVal * Updating this property will release all other icons. THIS.ClearIconList() THIS.AddIconToIconList(m.vNewVal) && Going from 0 to 1 icon updates systray immediately. ENDPROC PROCEDURE showballoontip LPARAMETERS lcBalloonText, lcBalloonTitle, lnIcon, lnTimeout * Displays a balloon tip if the Operating System shell supports it. * The lnTimeout parameter is the number of SECONDS to display the balloon. This * timeout period is subject to minimum and maximum values set by the operating * system (typically 10 and 30 seconds). * The lnIcon parameter specifies the icon to display in the * balloon. The values are as follows: * 0 = No icon * 1 = Info. A lowercase "i" in a small balloon * 2 = Warning. An exclamation point (!) in a triangle. * 3 = Error. A red disk with an X through it. * Add 16 (0x10) to the value to mute the sound that is played when * the balloon is displayed (Windows XP and later). * This method uses #DEFINES that are set in the AddIconToSystray method. IF PCOUNT() < 1 * Must at least include the balloon Text. RETURN -1 ENDIF IF THIS.ShellVersion < 5 &&GetShellVersion() < 5.00 * This shell doesn't support balloon tips. RETURN -1 ENDIF IF TYPE("m.lcBalloonText") <> "C" RETURN -1 ENDIF IF TYPE("m.lcBalloonTitle") <> "C" lcBalloonTitle = "" ENDIF IF TYPE("m.lnIcon") <> "N" lnIcon = 0 ENDIF IF TYPE("m.lnTimeout") <> "N" lnTimeout = 0 ELSE lnTimeOut = m.lnTimeout * 1000 && Convert seconds to Milliseconds. ENDIF LOCAL lcNotifyIconData, lnReturn WITH THIS * Build NOTIFYICONDATA structure compatible with version 5 of the shell. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON, NIF_INFO)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonText), 255),256, CHR(0)) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(m.lnTimeOut) && Timeout/Version lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonTitle), 63),64, CHR(0)) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(lnIcon, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * Send request to display balloon. This is asynchronous, so * balloon may not be displayed immediately. lnReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) ENDWITH ENDPROC PROCEDURE Destroy WITH THIS IF .Enabled .RemoveIconFromSystray() .ClearIconList() && Release resources. ENDIF ENDWITH ENDPROC PROCEDURE Init * If there is an initial icon specified, add it to the Icon List. IF TYPE("THIS.IconFile") = "C" AND LEN(THIS.IconFile) > 0 THIS.AddIconToIconList(THIS.IconFile) ENDIF * Add icon to systray if requested. IF this.AddIconToSystrayAtInit this.AddIconToSystray ENDIF ENDPROC 2 t2t2v%*0,Uq%.B-T9%j% Hx$ C  *       B {  B   B =  B ]  B } B  B2Bf Hb  *       B 6  B V  B2bBU NDIRECTIONNSHIFTNXCOORDNYCOORDTHIS SYSTRAYICONIDLEFT SHELLVERSIONICONCLICKEVENTICONDBLCLICKEVENTICONRIGHTCLICKEVENTICONMIDDLECLICKEVENTBALLOONSHOWEVENTBALLOONHIDEEVENTBALLOONTIMEOUTEVENTBALLOONCLICKEVENT%Cm.lhWndbN~H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC BB%CO% Ta%T%9  )%9%T9H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC U LHWNDSETFOREGROUNDWINDOWUSER32DLLWINAPI_SETFOREGROUNDWINDOWTHISFORMVISIBLE WINDOWSTATE SHOWWINDOWHWND %#B8| DestroyIcon user32.dllQWinAPI_DestroyIcon(CCC TTU THIS ICONCOUNT DESTROYICONUSER32DLLWINAPI_DESTROYICON NCURRENTICON AICONLISTCURRENTICONINDEX%C0 B-TC % SB- TT %TBaU CICONFILENAMENHANDLETHISLOADICON ICONCOUNT AICONLISTCURRENTICONINDEXt m%_%@T[T BUTHIS ICONCOUNTCURRENTICONINDEXN >%  ; B% )%C_screen.nSysTrayCountbUC nSysTrayCount9%9?T9T99T9@TCC ]gC ]F|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon%  T? TTCCTCTCC   TC TCC'TCCC_=C % TCTCTCC QTCTCC @QTCCTCC>% TC  %}%9pT9C ] B5TC9 MouseWheelreceiveIconEvent Ta%CTCCTCTCC   TC TCC#TCCC_=C TCTCTCC QTCTCC @QTCCTCC>TC  %W BT %C>?TC  % BT :TC  %61C% * Modifying item in tray failed!!!x B BULCNOTIFYICONDATATHIS ICONCOUNTCURRENTICONINDEXENABLED ADDPROPERTY NSYSTRAYCOUNT SYSTRAYICONIDNPREVMWACCRUALSHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICON SHELLVERSIONNTIPTEXTMAXLENGTHINTEGERTOSTRINGHWND AICONLISTTIPTEXTNRETURNGETSHELLVERSIONF|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon Q.C9 MouseWheelreceiveIconEvent T-TCCTC TCC   TC TC#TCCC _?=@C TCC>T C T %9 T9 C ]USHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICONCNOTIFYICONDATATHISENABLEDINTEGERTOSTRINGHWND SYSTRAYICONIDTIPTEXTNRETURN SHELLVERSION NSYSTRAYCOUNTNPREVMWACCRUAL     QFORMG|GetForegroundWindow user32.dllQWinAPI_GetForegroundWindowTC;| GetCursorPos user32.dllQWinAPI_GetCursorPosTCC QT CT CC =T CC \ T T T T!TCT  systray.vcx T a+ .TCx_frmInternalSystrayUseOnly T -% systray.vcxc(T CCC] systray.vcx T T - Hz T T  T T  NT T 2zT T   C"! H/ CtCm.lcMPRFileNamebC %Ct  < #$T#p1%(CtV"T# #, pC $_*DO (m.lcMPRFileName) WITH &lcParamList < & C'0  '<2()*+,-.T(C *'a;% (/<B-%C (G% (oT((<B-T)C]5/ T/s )CCW+( (T,CC + *gT-C + *T.C + zs. )" -[T0ON SELECTION BAR C ._ OF C )_ __systray_menu_return = C ,_&lcOnSelectionBar G t )<C/1 B/C !U2 LCMPRFILENAMEP1P2P3P4P5P6LHPREVIOUSWINDOWLCPOINTLNRETURNLNXCOORDLNYCOORD LCCLASSLIB LLCONTINUE LNTASKBARLEFT LNTASKBARTOPLNTASKBARRIGHTLNTASKBARBOTTOMLNTASKBARLOCATION LOTEMPFORMGETFOREGROUNDWINDOWUSER32DLLWINAPI_GETFOREGROUNDWINDOW GETCURSORPOSWINAPI_GETCURSORPOSTHISSTRINGTOINTEGERGETTASKBARPOSITIONTOPLEFTMENUOFFSETFROMRIGHTSHOWSETFOREGROUNDWINDOWHWND LCPARAMLISTLNCOUNTNCOUNT MENUTEXTISMPRMENUTEXT LNMENUITEMS LCMENUNAME LAMENUITEMSLNCURRENTMENUITEMLNTOKENLCTEXTLNBARNUM__SYSTRAY_MENU_RETURNLCONSELECTIONBARPROCESSMENUEVENT U NMENUITEMID(%CZ ! CUTHISMENUTEXTSHOWMENUQGBCC CCC CCC CCC UNINTEGERt>| LoadImage user32.dllQWinAPI_LoadImage%C05TCCtemp5 systray_temp_C].icn(B#T CTCTALKv SET TALK &cOldTalk % b?CFailed to load icon ''. in Ct   B U CICONFILENAMECOLDTALK CTEMPFILENAMEHIMAGE LOADIMAGEUSER32DLLWINAPI_LOADIMAGETHISERROR?TC %8 CUVNEWVALTHISTIPTEXTENABLEDADDICONTOSYSTRAY:T %3 UVNEWVALTHISCURRENTICONINDEXENABLEDADDICONTOSYSTRAY %Ct ) T  T( 2T CC \  BULCPDWORDLNBYTESLNRETURN LNCURBYTED|SHAppBarMessage shell32.dllQWinAPI_SHAppBarMessage  LTC TC TC TCC QTC TCC> T C TCC\ %XT ATCC\ %T ATCC\ %T ATCC\ %HT A H](     B    B    B2 BU NLEFTNTOPNRIGHTNBOTTOMSHAPPBARMESSAGESHELL32DLLWINAPI_SHAPPBARMESSAGE CAPPBARDATANRETURNTHISINTEGERTOSTRINGSTRINGTOINTEGERF| DllGetVersion shell32.dllQWinAPI_Shell32_DllGetVersion TCT CC QCTCC \ TCC  \ *BCC Z.CC ZgU DLLGETVERSIONSHELL32DLLWINAPI_SHELL32_DLLGETVERSIONLCDLLVERSIONINFOLNMAJORLNMINORTHISINTEGERTOSTRINGSTRINGTOINTEGER(%CZ ! CUTHISMENUTEXTSHOWMENU<T CC UVNEWVALTHISICONFILE CLEARICONLISTADDICONTOICONLIST%Ct . B% P B#%Cm.lcBalloonTextbC B$%Cm.lcBalloonTitlebC T%Cm.lnIconbN T%C m.lnTimeoutbN T/T   TCC TC "TCC    TC TCC #TCCC _=C TCTC%TCCC _=C TC $TCCC _?=@C TCCTCC>TC U LCBALLOONTEXTLCBALLOONTITLELNICON LNTIMEOUTTHIS SHELLVERSIONLCNOTIFYICONDATALNRETURNINTEGERTOSTRINGHWND SYSTRAYICONID AICONLISTCURRENTICONINDEXTIPTEXTWINAPI_SHELL_NOTIFYICON< 5%1 C CUTHISENABLEDREMOVEICONFROMSYSTRAY CLEARICONLISTi1%C THIS.IconFilebC C> DC%b UTHISICONFILEADDICONTOICONLISTADDICONTOSYSTRAYATINITADDICONTOSYSTRAYreceiveiconevent,setforegroundwindow cleariconlistaddicontoiconlist switchiconaddicontosystrayremoveiconfromsystrayshowmenubprocessmenueventiconclickeventintegertostringAloadicontiptext_assigncurrenticonindex_assignstringtointegerogettaskbarposition; getshellversion#iconrightclickeventR%iconfile_assign%showballoontip&Destroy)Init**11FrA1"DAAA!A!A!A!A!ABA!A!A!ABAAB4qAABTACA1AAA5!BAQAA5qqAs1qA1q!AAr7!11AAA4qAAAAQqZAiBADqq!aaaAS2AAR4Eq1aaaSADQAAARAAB3f}Cqa1AR2A3PquA!AAA1!a1c!1!"!A1qq!AqAq1qqA2qqAArQAarB32q4DA4qt4qQ!AA74a3A2qBA2q#A3A!A21K!aaaRAAAAAAAABA@1b!A3DA3q"131ABA2ABAAAAB!q1aaQAUA3AB41AA2, 7V lNN[vmx9#8O8j>>VK,VV~MVWQWXTX]f]^l^_r_a}a,kRk n;3n oS0o=p\bp{f{+|F|]})t2tiptext Text to be displayed in standard tooltip above the Taskbar Notification Area ("systray"). menutext Defines a menu to be displayed when user clicks on icon. Specify a numeric ID and text for each item, separating each by a semicolon. (e.g. 1;Activate; 2; Exit). The ID is passed to the ProcessMenuEvent method after the user has made a menu selecti menutextismpr Set to true if the MenuText property contains the name of a MPR to run. The MPR must define a shortcut menu. To pass parameters to the MPR, call ShowMenu method from the IconClick event. iconidentifier Used internally to store a unique id for our icon. This way, multiple instances of this class can be used without interfering with each other. iconcount systrayiconid Contains Unique ID for the systray token that this object creates. currenticonindex Determines which of the currently loaded icons is displayed in the Taskbar Notification Area ("systray"). enabled Is .T. when we have an icon in the Taskbar Notification Area ("systray"). iconfile If only one icon is used, it can be specified here. If multiple icons are to be used (animation), use the AddIconToIconList method. menuoffsetfromright Essentially the width of your menu in pixels. This is an offset used when the TaskBar is docked on the right, to prevent the menu from being drawn underneath the taskbar. addicontosystrayatinit If .T., the icon will be added to the Taskbar Notification Area ("systray") when this class is instantiated. If .F., you must call the AddIconToSystray() method. shellversion Contains the numeric version of the Shell communication method that we've established. nprevmwaccrual Previous setting of SYS(2060). *receiveiconevent Receives the events the Notify Icon. *setforegroundwindow By default, brings the form that contains this object to the foreground. You can also specify a different window to bring forward by passing a numeric hWnd parameter. *cleariconlist Clear all icons from the current list. If an icon is currently displayed in the taskbar notification area (systray), it is removed. *addicontoiconlist Adds an icon file to list of icons. Accepts a string parameter containing a filename. Call the SwitchIcon method to switch to the next icon. *switchicon Changes the displayed icon to the next icon in the icon list. Use AddIconToIconList method to populate the icon list, and ClearIconList to start over. *addicontosystray Adds the icon to the Taskbar Notification Area ("systray"). *removeiconfromsystray Removes the icon from the Taskbar Notification Area ("systray"). *showmenu Displays the menu defined by the MenuText property, or runs an MPR that defines a shortcut menu. Accepts string parameter containing the filename of the MPR to run, and optional second parameter to be passed to the MPR startup code. *processmenuevent This event is called if user has selected an item from a menu defined by the MenuText property. Add code here to act upon the user's selection. *iconclickevent Occurs when the user clicks on the icon in the Taskbar Notification Area ("systray"). *integertostring Converts an integer to its binary representation. *loadicon Loads an Icon from a file, and returns an hIcon token. ^aiconlist[1,0] Array containnig Icons and handles. *tiptext_assign *currenticonindex_assign *stringtointeger Convert binary representation of integer into an integer data type. *gettaskbarposition Retrieves coordinates of TaskBar. Also returns 0 if docked on bottom, 1 if docked left, 2 if docked top, 3 if docked right. *getshellversion Determines the Major and Minor version of shell32.dll. Used to determine if the shell supports balloon tips on taskbar icons. *balloonclickevent Occurs when user clicks on balloon tip. *balloonshowevent Occurs when the BalloonTip is shown. Balloon tips are queued by the system, so you balloon tip may not be displayed immediately after calling the DisplayBallloonTip method. *balloonhideevent Occurs if the user clicks on the close button of the balloon tip. *balloontimeoutevent Occurs if the system closes the balloon tip due to reaching the timeout. *iconrightclickevent Occurs when the user right-clicks on the icon in the Taskbar Notification Area ("systray"). *iconmiddleclickevent Occurs when the user clicks the icon using the middle mouse button. *icondblclickevent Occurs when the user double-clicks on the icon in the Taskbar Notification Area. *iconfile_assign *showballoontip Displays a balloon tip over the icon in the Taskbar Notification Area. Parameters: cBalloonText [, cBalloonTitle [, nIcon [, nTimeoutInSeconds]]] systray hyperlink1ClassPixelsiconidentifier^ iconcount^ enabled^ nprevmwaccrual^ integertostring^ loadicon^ aiconlist^ tiptext_assign^ stringtointeger^ iconfile_assign^ GoBack^ GoForward^ NavigateTo^ systray hyperlink hyperlinksystrayHeight = 23 Width = 23 tiptext = menutext = iconidentifier = 0 iconcount = 0 systrayiconid = 0 currenticonindex = 0 iconfile = menuoffsetfromright = 200 addicontosystrayatinit = .T. shellversion = 0 Name = "systray" }lPROCEDURE receiveiconevent LPARAMETERS nDirection, nShift, nXCoord, nYCoord * This is the procedure that receives the Notify Icon events. Since * we execute the BindEvent() function from within this class, this * procedure can be hidden. * nDirection contains our icon identifier. IF nDirection != this.SystrayIconID * Not our icon, or this a real MouseWheel event on the VFP window. RETURN .F. ENDIF * These are the events that we receive from the Taskbar icon. #DEFINE WM_MOUSEMOVE 0x0200 #DEFINE WM_LBUTTONDOWN 0x0201 #DEFINE WM_LBUTTONUP 0x0202 #DEFINE WM_LBUTTONDBLCLK 0x0203 #DEFINE WM_RBUTTONDOWN 0x0204 #DEFINE WM_RBUTTONUP 0x0205 #DEFINE WM_RBUTTONDBLCLK 0x0206 #DEFINE WM_MBUTTONDOWN 0x0207 #DEFINE WM_MBUTTONUP 0x0208 #DEFINE WM_MBUTTONDBLCLK 0x0209 * Mousewheel events also get passed, but are difficult to decipher. #define WM_CONTEXTMENU 0x007B && Same as RightClick, but used when && Version 5 events have been specified. && (See the DisplayBalloonTip method.) #define WM_USER 0x0400 #define NIN_SELECT WM_USER + 0 #define NINF_KEY 0x1 #define NIN_KEYSELECT BITOR(NIN_SELECT , NINF_KEY) * Balloon events supported on Windows ME and Windows XP, and later. Not supported on Win2k. #DEFINE NIN_BALLOONSHOW (WM_USER + 2) #DEFINE NIN_BALLOONHIDE (WM_USER + 3) #DEFINE NIN_BALLOONTIMEOUT (WM_USER + 4) #DEFINE NIN_BALLOONUSERCLICK (WM_USER + 5) * Even though this isn't a real MouseWheel event on the VFP window, * VFP gives us the coordinates with respect to the location of the * main VFP window. Must change back to global coordinates. nXCoord = nXCoord + _screen.Left IF nXCoord <> WM_MOUSEMOVE && Ignore mousemove events. * The Version 5 events are more difficult to deal with, * but Version 5 supports Balloon Tips. So the extra work * is worth it. IF THIS.ShellVersion >= 5 DO CASE CASE nXCoord = NIN_SELECT OR nXCoord = NIN_KEYSELECT THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_CONTEXTMENU && Shell version 5 and later THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN CASE nXCoord = NIN_BALLOONSHOW THIS.BalloonShowEvent RETURN CASE nXCoord = NIN_BALLOONHIDE THIS.BalloonHideEvent RETURN CASE nXCoord = NIN_BALLOONTIMEOUT THIS.BalloonTimeoutEvent RETURN CASE nXCoord = NIN_BALLOONUSERCLICK This.BalloonClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ELSE * Using Version 4 events. DO CASE CASE nXCoord = WM_LBUTTONDOWN THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_RBUTTONDOWN THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ENDIF ENDIF ENDPROC PROCEDURE setforegroundwindow LPARAMETERS lhWnd * Brings the specified top-level window to the top of the Windows z-order. * If no parameter specified, then THISFORM is made the foreground window. IF TYPE("m.lhWnd") = "N" DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(m.lhWnd) RETURN ENDIF RETURN * If we're here, then the user called this method without an * hWnd parameter. Assume it is thisform that is to be brought forward. IF VARTYPE(thisform) == "O" IF ! thisform.Visible thisform.Visible = .t. ENDIF * First make sure it isn't minimized. IF thisform.WindowState= 1 thisform.WindowState = 0 ENDIF * If necessary, make sure the main VFP window is not minimized. IF _screen.Visible AND thisform.ShowWindow = 0 IF _screen.WindowState = 1 _screen.WindowState = 0 && Set back to normal ENDIF ENDIF DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(thisform.HWnd) ENDIF ENDPROC PROCEDURE cleariconlist * Removes all icons from the icon list. * In detail: * Releases the resource handles of all icons that have been loaded, * clears the array and sets the count to zero. This method is called * by the Destroy event, to make sure all system resources are correctly * released. WITH this IF .IconCount = 0 * No icons in list RETURN ENDIF * Have to release all icon resources DECLARE INTEGER DestroyIcon IN user32.dll AS WinAPI_DestroyIcon ; INTEGER hIcon FOR nCurrentIcon = 1 TO ALEN(.aIconList,1) WinAPI_DestroyIcon(.aIconList[m.nCurrentIcon]) ENDFOR DIMENSION .aIconList[1] .IconCount = 0 .CurrentIconIndex = 0 ENDWITH ENDPROC PROCEDURE addicontoiconlist LPARAMETERS cIconFileName IF !FILE(cIconFileName) RETURN .f. ENDIF * We must load the icon as a Windows resource. nHandle = THIS.LoadIcon(m.cIconFileName) IF m.nHandle = 0 RETURN .f. ENDIF * We have a resource handle for the icon. Now we store that * handle (integer) in our array of icon resource handles. WITH THIS .IconCount = .IconCount + 1 DIMENSION .aIconList[.IconCount] .aIconList[.IconCount] = m.nHandle IF .CurrentIconIndex = 0 .CurrentIconIndex = 1 && Updates Systray immediately ENDIF ENDWITH RETURN .T. ENDPROC PROCEDURE switchicon * Switches to the next icon in the iconlist. * Typically called by a timer to regularly change the icon, * to create animation. WITH THIS IF .IconCount > 1 IF .CurrentIconIndex = .iconCount .CurrentIconIndex = 1 ELSE .CurrentIconIndex = .CurrentIconIndex + 1 ENDIF ENDIF RETURN .CurrentIconIndex ENDWITH ENDPROC PROCEDURE addicontosystray *!* This is where the magic happens. *!* This is an unabashed hack to allow us to get the full functionality *!* of the Taskbar Notification Area ("System Tray") without having to *!* use an external C++ library. *!* To communicate with the systray, we tell it to send us messages via *!* the MouseWheel event. (This is the only VFP event that doesn't alter *!* or discard event data before firing the corresponding internal event.) *!* The second trick is that only the main VFP window will accept the events *!* without checking to see if the event coordinates are invalid. (The screen *!* doesn't even need to be visible, so you can have SCREEN=OFF in your *!* config.fpw file.) So we use VFP8's BINDEVENT() function to bind to the *!* _SCREEN.MouseWheel event. *!* This method sets up that communication path. *!* Returns 1 if successful. LOCAL lcNotifyIconData ** NOTIFYICON struct defines #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 && Win2k and later. #define NIF_INFO 0x00000010 && Use balloon tip. Win2k and later. && Notify Icon Infotip flags #define NIIF_NONE 0x00000000 && icon flags are mutually exclusive && and take only the lowest 2 bits #define NIIF_INFO 0x00000001 #define NIIF_WARNING 0x00000002 #define NIIF_ERROR 0x00000003 #define NIIF_ICON_MASK 0x0000000F #define NIIF_NOSOUND 0x00000010 && Windows XP and later. #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIM_SETFOCUS 0x00000003 && Windows 2000 and later. #define NIM_SETVERSION 0x00000004 && Windows 2000 and later. #define NOTIFYICON_VERSION 3 #define NIS_HIDDEN 0x00000001 #define NIS_SHAREDICON 0x00000002 WITH THIS * If no icons loaded, do nothing. IF .IconCount < 1 OR .CurrentIconIndex = 0 RETURN 0 ENDIF IF NOT .Enabled * Each SystemTray icon in this process requires a unique ID. We'll * use an _screen property to make sure each instance of this class * gets its own ID. Properties on _screen aren't affected by CLEAR ALL. * Because VFP drops the least significant two bytes of the mousewheel event's * WPARAM parameter, our unique SysTray Icon ID must use the most significant * two bytes: #DEFINE MIN_SYSTRAY_ICON_ID 0x4000 IF TYPE("_screen.nSysTrayCount") == "U" * We're the first one here. _screen.AddProperty("nSysTrayCount", 0) ELSE IF _Screen.nSysTrayCount > 0x3FFF * User is apparently creating and destroying this object repeatedly. _Screen.nSysTrayCount = 0 ENDIF ENDIF _screen.nSysTrayCount = _screen.nSysTrayCount + 1 this.SystrayIconID = _screen.nSysTrayCount + MIN_SYSTRAY_ICON_ID * By default, VFP combines MouseWheel events. (That is, if we receive * a MouseWheel event from the OS, we check our internal queue to see * if there is already a MouseWheel event that hasn't been processed yet. * If found, we just add the number of MouseWheel turns to the existing * event.) This behavior makes our communication with the SystemTray * unstable, so we must disable this combining of events. SYS(2060) * accomplishes this: THIS.nPrevMWAccrual = VAL(SYS(2060)) SYS(2060, 1) ENDIF * Declare the WinAPI function that lets us install the icon. * We redeclare the function every time, just in case CLEAR DLLS is called * elsewhere in the app. DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; STRING @ PNOTIFYICONDATA IF THIS.ShellVersion = 0 && If we haven't set the version yet. nTipTextMaxLength = 63 && Default to version 4. ELSE nTipTextMaxLength = 127 ENDIF * Build NOTIFYICONDATA structure. lcNotifyIconData = .IntegerToString(_VFP.Hwnd) && Messages get sent to VFP's main window. lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), nTipTextMaxLength ), nTipTextMaxLength + 1, CHR(0)) && TipText IF .ShellVersion >= 5 lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags ENDIF lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure IF NOT .Enabled && If not already in Taskbar Notification Area.... * Add icon to Taskbar Notification Area. nReturn = WinAPI_Shell_NotifyIcon( NIM_ADD, @lcNotifyIconData) IF nReturn <> 1 * Adding item to tray failed!!! IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, .nPrevMWAccrual) && Reset mousewheel behavior. ENDIF RETURN nReturn ENDIF * Bind to the MouseWheel event on VFP's main window. nReturn = BINDEVENT(_screen,"MouseWheel",this,"receiveIconEvent",2) .Enabled = .t. * We've just added the icon. Now we see if we can set to Version 5 shell behavior, * with larger tooltip lengths and support for balloon Tips. IF .GetShellVersion() >= 5 * This OS supports version 5 features. Switch to version 5. * We have to inform the Shell that we want to use Version 5 features. * Build a version 5 structure for sending message to change version. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * First we check to see if we can switch to using version 5 events. nReturn = WinAPI_Shell_NotifyIcon( NIM_SETVERSION, @lcNotifyIconData) IF nReturn <> 1 * Couldn't switch to version 5 events RETURN -1 ENDIF .ShellVersion = 5 * Now that we've updated to Version 5, we have to modify the existing icon * if the .TipText is longer than 63 characters. IF LEN(.TipText) > 63 nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 RETURN nReturn ENDIF ENDIF ELSE .ShellVersion = 4 ENDIF ELSE * Already in the Taskbar Notification Area. Just update existing icon. nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 MESSAGEBOX(" * Modifying item in tray failed!!!") RETURN nReturn ENDIF ENDIF ENDWITH RETURN nReturn ENDPROC PROCEDURE removeiconfromsystray * Removes the icon from the Taskbar Notification Area ('System Tray'). DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; string @ PNOTIFYICONDATA #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 #define NIF_INFO 0x00000010 LOCAL cNotifyIconData WITH THIS UNBINDEVENTS(_screen, "MouseWheel", this, "receiveIconEvent") .Enabled = .f. IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, .nPrevMWAccrual) && Reset mousewheel behavior. ENDIF * Build structure. cNotifyIconData = .IntegerToString(_vfp.hwnd) cNotifyIconData = cNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) cNotifyIconData = cNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE, ,NIF_TIP cNotifyIconData = cNotifyIconData + .IntegerToString(0x20A) && uCallback cNotifyIconData = cNotifyIconData + .IntegerToString(0) && icon handle cNotifyIconData = cNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 63), 64, CHR(0)) cNotifyIconData = .integertostring(LEN(cNotifyIconData) + 4) + cNotifyIconData ENDWITH nReturn = WinAPI_Shell_NotifyIcon(NIM_DELETE, @cNotifyIconData) THIS.ShellVersion = 0 ENDPROC PROCEDURE showmenu LPARAMETERS lcMPRFileName, p1, p2, p3, p4, p5, p6 * Displays the shortcut menu near the taskbar icon. * If the menu is defined as a set of tokens and text strings, the * menu is defined here. Otherwise, the specified MPR is called. * If the first parameter is blank or not specified, the contents * of the MenuText property are used to define/determine the menu. * If the first parameter is not blank, it is assumed to specify * the MPR to run. The MenuText property is ignored. * * The additional parameters are passed on to the MPR. They are only * used if the cMPRFileName parameter is specified and non-blank. * * In order to display the shortcut menu in the proper location, * we must activate it within a toplevel form (with .visible = .t.), * near the taskbar. * On WindowsME, Win2k and later, the form is made invisible by * calling a Windows API function. (See the INIT of the form class.) * But on Win98 and NT4, that API function didn't exist, so we make it * a "Desktop" form instead. This allows us to remove the titlebar and * make the form so small it won't be noticed. But it causes slightly * incorrect behavior when trying to clear the menu. LOCAL lhPreviousWindow, lcPOINT, lnReturn, lnXCoord, lnYCoord, lcClassLib, llContinue LOCAL lnTaskBarLeft, lnTaskBarTop, lnTaskBarRight, lnTaskBarBottom, lnTaskBarLocation LOCAL loTempForm as Form * Our invisible form must be made the top window in order to make the shortcut * menu get cleared properly if user clicks elsewhere. Therefore, we store the * handle of the current foreground window, so we can restore it later. DECLARE INTEGER GetForegroundWindow IN user32.dll AS WinAPI_GetForegroundWindow lhPreviousWindow = WinAPI_GetForegroundWindow() * Get location of the mouse pointer. DECLARE INTEGER GetCursorPos IN user32.dll AS WinAPI_GetCursorPos STRING @ lcPOINT = REPLICATE(CHR(0), 8) lnReturn = WinAPI_GetCursorPos(@lcPOINT) lnXCoord = THIS.StringToInteger(LEFT(m.lcPOINT,4)) lnYCoord = THIS.StringToInteger(SUBSTR(m.lcPOINT, 5)) * Get the position and coordinates of the taskbar. lnTaskBarLeft = 0 lnTaskBarRight = 0 lnTaskBarTop = 0 lnTaskBarBottom = 0 lnTaskBarLocation = THIS.GetTaskBarPosition(@lnTaskBarLeft, @lnTaskBarTop, @lnTaskBarRight, @lnTaskBarBottom) * Need Top-Level form. * It also has ShowInTaskBar=.F. so we don't get a large button on the taskbar. * It is also invisible, using the SetWindowLong API function in the form's INIT. lcClassLib = "systray.vcx" llContinue = .T. DO WHILE llContinue TRY loTempForm = NEWOBJECT("x_frmInternalSystrayUseOnly", lcClassLib ) llContinue = .F. CATCH IF lcClassLib = "systray.vcx" * Try to create the form by looking in the same place as this object lcClassLib = ADDBS(JUSTPATH(SYS(1271, this))) + "systray.vcx" ELSE loTempForm = .Null. llContinue = .F. ENDIF ENDTRY ENDDO * Now position form near mouse, but adjust for taskbar. DO CASE CASE m.lnTaskBarLocation = 1 && Left loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarRight + 7 CASE m.lnTaskBarLocation = 2 && Top * Ideally would determine height of titlebar, and subtract that. * But this is sufficient. loTempForm.top = m.lnTaskBarBottom - 8 loTempForm.Left = m.lnXCoord CASE m.lnTaskBarLocation = 3 && Right loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarLeft - this.MenuOffsetFromRight && Roughly the width of a typical menu. OTHERWISE && Bottom, or coding error. * Just place the window near the mouse. loTempForm.top = m.lnYCoord loTempForm.Left = m.lnXCoord ENDCASE * Window must be the foreground window when shortcut menu displayed. Otherwise, * clicking in another window won't release the shortcut menu. loTempForm.Show THIS.SetForegroundWindow(loTempForm.hWnd) * Now display the menu. DO CASE CASE (PCOUNT() > 0 AND TYPE("m.lcMPRFileName") = "C") * Want to run the MPR specified in parameter IF PCOUNT() = 1 DO (m.lcMPRFileName) RELEASE loTempForm ELSE * Build parameter list LOCAL lcParamList, lnCount lcParamList = "p1" FOR nCount = 3 TO PCOUNT() && if PCOUNT() is only 2, don't enter loop. lcParamList = m.lcParamList + ", p" + TRANSFORM(m.lnCount - 1) ENDFOR DO (m.lcMPRFileName) WITH &lcParamList RELEASE loTempForm ENDIF CASE THIS.MenuTextIsMPR AND FILE(THIS.MenuText) * We want to run the MPR specified in MenuText property. DO (THIS.MenuText) RELEASE loTempForm OTHERWISE * Build menu from this.MenuText. LOCAL lnMenuItems, lcMenuName, laMenuItems[1], lnCurrentMenuItem LOCAL lnToken, lcText, lnBarNum lnMenuItems = ALINES(m.laMenuItems, THIS.MenuText, .T., ";") IF m.lnMenuItems = 0 RELEASE loTempForm RETURN .f. ENDIF IF m.lnMenuItems % 2 != 0 * Incorrect number of items in MenuText property. IF m.lnMenuItems > 1 lnMenuItems = lnMenuItems - 1 ELSE RELEASE loTempForm RETURN .f. ENDIF ENDIF lcMenuName = SYS(2015) * Make private variable to receive the ID of the selected menu item. PRIVATE __systray_menu_return __systray_menu_return = 0 * Build the menu. DEFINE POPUP (m.lcMenuName) SHORTCUT RELATIVE FROM MROW(),MCOL() FOR lnCurrentMenuItem = 1 TO m.lnMenuItems STEP 2 lnToken = VAL(m.laMenuItems[m.lnCurrentMenuItem]) lcText = m.laMenuItems[m.lnCurrentMenuItem + 1] lnBarNum = FLOOR(m.lnCurrentMenuItem/2) + 1 DEFINE BAR (lnBarNum) OF (m.lcMenuName) PROMPT (m.lcText) lcOnSelectionBar = "ON SELECTION BAR " + TRANSFORM(m.lnBarNum) + " OF " + ; TRANSFORM(m.lcMenuName) + " __systray_menu_return = " + TRANSFORM(m.lnToken) &lcOnSelectionBar ENDFOR SET ESCAPE off * Display the menu and wait for user action. ACTIVATE POPUP (m.lcMenuName) RELEASE loTempForm THIS.ProcessMenuEvent(__systray_menu_return) RETURN __systray_menu_return ENDCASE * Menu has been deactivated. Restore previous foreground window. This.SetForegroundWindow(m.lhPreviousWindow) ENDPROC PROCEDURE processmenuevent LPARAMETERS nMenuItemID * This event is fired after a menu has been clicked or cleared. ENDPROC PROCEDURE iconclickevent * This "event" is called when the user left-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE integertostring LPARAMETERS nInteger * Changes an integer into the Intel representation of that integer. * Used to build structures. RETURN CHR(BITAND(nInteger, 255)) + CHR(BITAND(BITRSHIFT(nInteger, 8), 255)) + ; CHR(BITAND(BITRSHIFT(nInteger, 16), 255)) + CHR(BITAND(BITRSHIFT(nInteger, 24), 255)) ENDPROC PROCEDURE loadicon LPARAMETERS cIconFileName * Loads an icon as a Windows resource. Returns the resource handle. LOCAL cOldTalk, cTempFileName, hImage DECLARE INTEGER LoadImage IN user32.dll AS WinAPI_LoadImage ; INTEGER hinst, ; STRING lpszName, ; INTEGER uType, ; && uint INTEGER cxDesired, ; INTEGER cyDesired, ; INTEGER fuLoad && uint ** LoadImage defines **** #define IMAGE_ICON 1 #define LR_LOADFROMFILE 0x0010 * Assume that the icon is contained in the app/exe file. Copy to temp before loading. IF FILE(cIconFileName) cTempFileName = ADDBS(GETENV("temp")) + "systray_temp_" + SYS(3) + ".icn" COPY FILE (cIconFileName) TO (cTempFileName) ELSE RETURN ENDIF m.hImage = WinAPI_LoadImage( 0, ; && Don't load from resource file cTempFileName, ; IMAGE_ICON, ; 0, ; 0, ; LR_LOADFROMFILE) * Now need to erase the temp file. SET TALK OFF to avoid the "File has been erased" * message on the status bar. cOldTalk = SET("TALK") ERASE (cTempFileName) SET TALK &cOldTalk IF m.hImage = 0 this.Error(0, "Failed to load icon '" + cIconFileName + "'. in " + PROGRAM(), LINENO()) ENDIF RETURN m.hImage ENDPROC PROCEDURE tiptext_assign LPARAMETERS vNewVal * The text for the tooltip is being changed. THIS.tiptext = ALLTRIM(m.vNewVal) IF THIS.Enabled * This icon is already displayed in the Taskbar Notification Area, * so we must update the systray immediately: this.AddIconToSystray() endif ENDPROC PROCEDURE currenticonindex_assign LPARAMETERS vNewVal * This is called when user changes the CurrentIconIndex property. THIS.currenticonindex = m.vNewVal IF this.Enabled * We're attached to systray. Update immediately. this.AddIconToSystray ENDIF ENDPROC PROCEDURE stringtointeger LPARAMETERS lcPDWORD, lnBytes * Converts a string from the Intel int, DWORD,WORD, and BYTE formats * into a VFP integer. IF PCOUNT() < 2 lnBytes = 4 && No length provided, assume 4-byte integer. ENDIF LOCAL lnReturn, lnCurByte lnReturn = 0 FOR lnCurByte = 1 to m.lnBytes lnReturn = m.lnReturn + ASC(SUBSTR(m.lcPDWord, m.lnCurByte, 1))*(256^(m.lnCurByte-1)) ENDFOR RETURN lnReturn ENDPROC PROCEDURE gettaskbarposition LPARAMETERS nLeft, nTop, nRight, nBottom * We position any menus near where the mouse just clicked. But we need to * fine-tune that position depending on which edge of the screen the * user has docked the taskbar. This method retrieves the absolute * coordinates of the taskbar, and derives from those coordinates which * edge the taskbar is on. #DEFINE ABM_GETTASKBARPOS 0x00000005 DECLARE INTEGER SHAppBarMessage IN shell32.dll as WinAPI_SHAppBarMessage ; INTEGER dwMessage, STRING @ PAPPBARDATA LOCAL cAPPBARDATA, nReturn WITH THIS cAPPBARDATA = .IntegerToString(0) && hWnd cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && uCallBackMsg cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && Edge cAPPBARDATA = cAPPBARDATA + REPLICATE(CHR(0), 4*4) && RECT structure cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && lParam cAPPBARDATA = .IntegerToString(LEN(cAPPBARDATA) + 4) + cAPPBARDATA nReturn = WinAPI_SHAppBarMessage(ABM_GETTASKBARPOS, @cAPPBARDATA) * Taskbar coordinates should be in RECT structure. nLeft = .StringToInteger(SUBSTR(cAPPBARDATA, 17, 4)) IF nLeft > 0xFFFFFF && If the number is ridiculously large, assume it was a negative number. nLeft = 0 - (0xFFFFFFFF - nLeft) ENDIF nTop = .StringToInteger(SUBSTR(cAPPBARDATA, 21, 4)) IF nTop > 0xFFFFFF nTop = 0 - (0xFFFFFFFF - nTop) ENDIF nRight = .StringToInteger(SUBSTR(cAPPBARDATA, 25, 4)) IF nRight > 0xFFFFFF nRight = 0 - (0xFFFFFFFF - nRight) ENDIF nBottom = .StringToInteger(SUBSTR(cAPPBARDATA, 29, 4)) IF nBottom > 0xFFFFFF nBottom = 0 - (0xFFFFFFFF - nBottom) ENDIF ENDWITH DO CASE * Must use approximations for taskbar coordinates, because they oare slightly different * on different versions of Windows. CASE nLeft < 2 AND nTop < 2 AND nRight < 479 && Roughly minimum width in portrait mode. RETURN 1 && Left CASE nLeft < 2 AND nTop > 0 RETURN 0 && Bottom CASE nLeft < 2 AND nTop < 2 && Must be top, otherwise first case would take it. RETURN 2 && Top OTHERWISE RETURN 3 && Right ENDCASE *!* typedef struct _AppBarData { *!* DWORD cbSize; *!* HWND hWnd; *!* UINT uCallbackMessage; *!* UINT uEdge; *!* RECT rc; *!* LPARAM lParam; *!* } APPBARDATA, *PAPPBARDATA *!* typedef struct _RECT { *!* LONG left; *!* LONG top; *!* LONG right; *!* LONG bottom; *!* } RECT ENDPROC PROCEDURE getshellversion DECLARE INTEGER DllGetVersion IN shell32.dll AS WinAPI_Shell32_DllGetVersion ; STRING @ DLLVERSIONINFO * Windows 98 typically returns 4.72, depending on version of IE installed. * Windows ME returns 5.5 or greater. * Windows 2000 returns 5.0 * Windows XP returns 6.0 LOCAL lcDLLVersionInfo, lnMajor, lnMinor WITH THIS lcDLLVersionInfo = .IntegerToString(20) lcDLLVersionInfo = m.lcDLLVersionInfo + REPLICATE(CHR(0), 16) WinAPI_Shell32_DllGetVersion(@lcDLLVersionInfo) lnMajor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 5, 4)) lnMinor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 9, 4)) RETURN VAL(STR(m.lnMajor, 2, 0) + "." + ALLTRIM(STR(m.lnMinor,3,0))) ENDWITH ENDPROC PROCEDURE iconrightclickevent * This "event" is called when the user right-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE iconfile_assign LPARAMETERS vNewVal *To do: Modify this routine for the Assign method THIS.ICONFILE = m.vNewVal * Updating this property will release all other icons. THIS.ClearIconList() THIS.AddIconToIconList(m.vNewVal) && Going from 0 to 1 icon updates systray immediately. ENDPROC PROCEDURE showballoontip LPARAMETERS lcBalloonText, lcBalloonTitle, lnIcon, lnTimeout * Displays a balloon tip if the Operating System shell supports it. * The lnTimeout parameter is the number of SECONDS to display the balloon. This * timeout period is subject to minimum and maximum values set by the operating * system (typically 10 and 30 seconds). * The lnIcon parameter specifies the icon to display in the * balloon. The values are as follows: * 0 = No icon * 1 = Info. A lowercase "i" in a small balloon * 2 = Warning. An exclamation point (!) in a triangle. * 3 = Error. A red disk with an X through it. * Add 16 (0x10) to the value to mute the sound that is played when * the balloon is displayed (Windows XP and later). * This method uses #DEFINES that are set in the AddIconToSystray method. IF PCOUNT() < 1 * Must at least include the balloon Text. RETURN -1 ENDIF IF THIS.ShellVersion < 5 &&GetShellVersion() < 5.00 * This shell doesn't support balloon tips. RETURN -1 ENDIF IF TYPE("m.lcBalloonText") <> "C" RETURN -1 ENDIF IF TYPE("m.lcBalloonTitle") <> "C" lcBalloonTitle = "" ENDIF IF TYPE("m.lnIcon") <> "N" lnIcon = 0 ENDIF IF TYPE("m.lnTimeout") <> "N" lnTimeout = 0 ELSE lnTimeOut = m.lnTimeout * 1000 && Convert seconds to Milliseconds. ENDIF LOCAL lcNotifyIconData, lnReturn WITH THIS * Build NOTIFYICONDATA structure compatible with version 5 of the shell. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON, NIF_INFO)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonText), 255),256, CHR(0)) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(m.lnTimeOut) && Timeout/Version lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonTitle), 63),64, CHR(0)) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(lnIcon, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * Send request to display balloon. This is asynchronous, so * balloon may not be displayed immediately. lnReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) ENDWITH ENDPROC PROCEDURE Init * If there is an initial icon specified, add it to the Icon List. IF TYPE("THIS.IconFile") = "C" AND LEN(THIS.IconFile) > 0 THIS.AddIconToIconList(THIS.IconFile) ENDIF * Add icon to systray if requested. IF this.AddIconToSystrayAtInit this.AddIconToSystray ENDIF ENDPROC PROCEDURE Destroy WITH THIS IF .Enabled .RemoveIconFromSystray() .ClearIconList() && Release resources. ENDIF ENDWITH ENDPROC 2 t2t2v%*0,Uq%.B-T9%j% Hx$ C  *       B {  B   B =  B ]  B } B  B2Bf Hb  *       B 6  B V  B2bBU NDIRECTIONNSHIFTNXCOORDNYCOORDTHIS SYSTRAYICONIDLEFT SHELLVERSIONICONCLICKEVENTICONDBLCLICKEVENTICONRIGHTCLICKEVENTICONMIDDLECLICKEVENTBALLOONSHOWEVENTBALLOONHIDEEVENTBALLOONTIMEOUTEVENTBALLOONCLICKEVENT%Cm.lhWndbN~H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC BB%CO% Ta%T%9  )%9%T9H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC U LHWNDSETFOREGROUNDWINDOWUSER32DLLWINAPI_SETFOREGROUNDWINDOWTHISFORMVISIBLE WINDOWSTATE SHOWWINDOWHWND %#B8| DestroyIcon user32.dllQWinAPI_DestroyIcon(CCC TTU THIS ICONCOUNT DESTROYICONUSER32DLLWINAPI_DESTROYICON NCURRENTICON AICONLISTCURRENTICONINDEX%C0 B-TC % SB- TT %TBaU CICONFILENAMENHANDLETHISLOADICON ICONCOUNT AICONLISTCURRENTICONINDEXt m%_%@T[T BUTHIS ICONCOUNTCURRENTICONINDEXN >%  ; B% )%C_screen.nSysTrayCountbUC nSysTrayCount9%9?T9T99T9@TCC ]gC ]F|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon%  T? TTCCTCTCC   TC TCC'TCCC_=C % TCTCTCC QTCTCC @QTCCTCC>% TC  %}%9pT9C ] B5TC9 MouseWheelreceiveIconEvent Ta%CTCCTCTCC   TC TCC#TCCC_=C TCTCTCC QTCTCC @QTCCTCC>TC  %W BT %C>?TC  % BT :TC  %61C% * Modifying item in tray failed!!!x B BULCNOTIFYICONDATATHIS ICONCOUNTCURRENTICONINDEXENABLED ADDPROPERTY NSYSTRAYCOUNT SYSTRAYICONIDNPREVMWACCRUALSHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICON SHELLVERSIONNTIPTEXTMAXLENGTHINTEGERTOSTRINGHWND AICONLISTTIPTEXTNRETURNGETSHELLVERSIONF|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon .C9 MouseWheelreceiveIconEvent T-%9T9C ]TCC TC  TCC    TC  TC #TCCC _?=@C TCC> T C TUSHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICONCNOTIFYICONDATATHISENABLED NSYSTRAYCOUNTNPREVMWACCRUALINTEGERTOSTRINGHWND SYSTRAYICONIDTIPTEXTNRETURN SHELLVERSION     QFORMG|GetForegroundWindow user32.dllQWinAPI_GetForegroundWindowTC;| GetCursorPos user32.dllQWinAPI_GetCursorPosTCC QT CT CC =T CC \ T T T T!TCT  systray.vcx T a+ .TCx_frmInternalSystrayUseOnly T -% systray.vcxc(T CCC] systray.vcx T T - Hz T T  T T  NT T 2zT T   C"! H/ CtCm.lcMPRFileNamebC %Ct  < #$T#p1%(CtV"T# #, pC $_*DO (m.lcMPRFileName) WITH &lcParamList < & C'0  '<2()*+,-.T(C *'a;% (/<B-%C (G% (oT((<B-T)C]5/ T/s )CCW+( (T,CC + *gT-C + *T.C + zs. )" -[T0ON SELECTION BAR C ._ OF C )_ __systray_menu_return = C ,_&lcOnSelectionBar G t )<C/1 B/C !U2 LCMPRFILENAMEP1P2P3P4P5P6LHPREVIOUSWINDOWLCPOINTLNRETURNLNXCOORDLNYCOORD LCCLASSLIB LLCONTINUE LNTASKBARLEFT LNTASKBARTOPLNTASKBARRIGHTLNTASKBARBOTTOMLNTASKBARLOCATION LOTEMPFORMGETFOREGROUNDWINDOWUSER32DLLWINAPI_GETFOREGROUNDWINDOW GETCURSORPOSWINAPI_GETCURSORPOSTHISSTRINGTOINTEGERGETTASKBARPOSITIONTOPLEFTMENUOFFSETFROMRIGHTSHOWSETFOREGROUNDWINDOWHWND LCPARAMLISTLNCOUNTNCOUNT MENUTEXTISMPRMENUTEXT LNMENUITEMS LCMENUNAME LAMENUITEMSLNCURRENTMENUITEMLNTOKENLCTEXTLNBARNUM__SYSTRAY_MENU_RETURNLCONSELECTIONBARPROCESSMENUEVENT U NMENUITEMID(%CZ ! CUTHISMENUTEXTSHOWMENUQGBCC CCC CCC CCC UNINTEGERt>| LoadImage user32.dllQWinAPI_LoadImage%C05TCCtemp5 systray_temp_C].icn(B#T CTCTALKv SET TALK &cOldTalk % b?CFailed to load icon ''. in Ct   B U CICONFILENAMECOLDTALK CTEMPFILENAMEHIMAGE LOADIMAGEUSER32DLLWINAPI_LOADIMAGETHISERROR?TC %8 CUVNEWVALTHISTIPTEXTENABLEDADDICONTOSYSTRAY:T %3 UVNEWVALTHISCURRENTICONINDEXENABLEDADDICONTOSYSTRAY %Ct ) T  T( 2T CC \  BULCPDWORDLNBYTESLNRETURN LNCURBYTED|SHAppBarMessage shell32.dllQWinAPI_SHAppBarMessage  LTC TC TC TCC QTC TCC> T C TCC\ %XT ATCC\ %T ATCC\ %T ATCC\ %HT A H](     B    B    B2 BU NLEFTNTOPNRIGHTNBOTTOMSHAPPBARMESSAGESHELL32DLLWINAPI_SHAPPBARMESSAGE CAPPBARDATANRETURNTHISINTEGERTOSTRINGSTRINGTOINTEGERF| DllGetVersion shell32.dllQWinAPI_Shell32_DllGetVersion TCT CC QCTCC \ TCC  \ *BCC Z.CC ZgU DLLGETVERSIONSHELL32DLLWINAPI_SHELL32_DLLGETVERSIONLCDLLVERSIONINFOLNMAJORLNMINORTHISINTEGERTOSTRINGSTRINGTOINTEGER(%CZ ! CUTHISMENUTEXTSHOWMENU<T CC UVNEWVALTHISICONFILE CLEARICONLISTADDICONTOICONLIST%Ct . B% P B#%Cm.lcBalloonTextbC B$%Cm.lcBalloonTitlebC T%Cm.lnIconbN T%C m.lnTimeoutbN T/T   TCC TC "TCC    TC TCC #TCCC _=C TCTC%TCCC _=C TC $TCCC _?=@C TCCTCC>TC U LCBALLOONTEXTLCBALLOONTITLELNICON LNTIMEOUTTHIS SHELLVERSIONLCNOTIFYICONDATALNRETURNINTEGERTOSTRINGHWND SYSTRAYICONID AICONLISTCURRENTICONINDEXTIPTEXTWINAPI_SHELL_NOTIFYICONi1%C THIS.IconFilebC C> DC%b UTHISICONFILEADDICONTOICONLISTADDICONTOSYSTRAYATINITADDICONTOSYSTRAY< 5%1 C CUTHISENABLEDREMOVEICONFROMSYSTRAY CLEARICONLISTreceiveiconevent,setforegroundwindow cleariconlistaddicontoiconlist switchiconaddicontosystrayremoveiconfromsystrayshowmenubprocessmenueventiconclickeventintegertostringAloadicontiptext_assigncurrenticonindex_assignstringtointegerogettaskbarposition; getshellversion#iconrightclickeventR%iconfile_assign%showballoontip&Init)Destroyo*11FrA1"DAAA!A!A!A!A!ABA!A!A!ABAAB4qAABTACA1AAA5!BAQAA5qqAs1qA1q!AAr7!11AAA4qAAAAQqZAiBADqq!aaaAS2AAR4Eq1aaaSADQAAARAAB3f}2ACqa1AR5PquA!AAA1!a1c!1!"!A1qq!AqAq1qqA2qqAArQAarB32q4DA4qt4qQ!AA74a3A2qBA2q#A3A!A21K!aaaRAAAAAAAABA@1b!A3DA3q"131ABA2ABAAAAB!q1aaQAUA31AA3AB3, 7V lNN[vmx9#8O8n>> VK0VVMVWQWXTX]f]^l^_r`a}a0kVk n=7noU4oAp^fp{h{||a})t2tiptext Text to be displayed in standard tooltip above the Taskbar Notification Area ("systray"). menutext Defines a menu to be displayed when user clicks on icon. Specify a numeric ID and text for each item, separating each by a semicolon. (e.g. 1;Activate; 2; Exit). The ID is passed to the ProcessMenuEvent method after the user has made a menu selecti menutextismpr Set to true if the MenuText property contains the name of a MPR to run. The MPR must define a shortcut menu. To pass parameters to the MPR, call ShowMenu method from the IconClick event. iconidentifier Used internally to store a unique id for our icon. This way, multiple instances of this class can be used without interfering with each other. iconcount systrayiconid Contains Unique ID for the systray token that this object creates. currenticonindex Determines which of the currently loaded icons is displayed in the Taskbar Notification Area ("systray"). enabled Is .T. when we have an icon in the Taskbar Notification Area ("systray"). iconfile If only one icon is used, it can be specified here. If multiple icons are to be used (animation), use the AddIconToIconList method. menuoffsetfromright Essentially the width of your menu in pixels. This is an offset used when the TaskBar is docked on the right, to prevent the menu from being drawn underneath the taskbar. addicontosystrayatinit If .T., the icon will be added to the Taskbar Notification Area ("systray") when this class is instantiated. If .F., you must call the AddIconToSystray() method. shellversion Contains the numeric version of the Shell communication method that we've established. nprevmwaccrual Previous setting of SYS(2060). *receiveiconevent Receives the events the Notify Icon. *setforegroundwindow By default, brings the form that contains this object to the foreground. You can also specify a different window to bring forward by passing a numeric hWnd parameter. *cleariconlist Clear all icons from the current list. If an icon is currently displayed in the taskbar notification area (systray), it is removed. *addicontoiconlist Adds an icon file to list of icons. Accepts a string parameter containing a filename. Call the SwitchIcon method to switch to the next icon. *switchicon Changes the displayed icon to the next icon in the icon list. Use AddIconToIconList method to populate the icon list, and ClearIconList to start over. *addicontosystray Adds the icon to the Taskbar Notification Area ("systray"). *removeiconfromsystray Removes the icon from the Taskbar Notification Area ("systray"). *showmenu Displays the menu defined by the MenuText property, or runs an MPR that defines a shortcut menu. Accepts string parameter containing the filename of the MPR to run, and optional second parameter to be passed to the MPR startup code. *processmenuevent This event is called if user has selected an item from a menu defined by the MenuText property. Add code here to act upon the user's selection. *iconclickevent Occurs when the user clicks on the icon in the Taskbar Notification Area ("systray"). *integertostring Converts an integer to its binary representation. *loadicon Loads an Icon from a file, and returns an hIcon token. ^aiconlist[1,0] Array containnig Icons and handles. *tiptext_assign *currenticonindex_assign *stringtointeger Convert binary representation of integer into an integer data type. *gettaskbarposition Retrieves coordinates of TaskBar. Also returns 0 if docked on bottom, 1 if docked left, 2 if docked top, 3 if docked right. *getshellversion Determines the Major and Minor version of shell32.dll. Used to determine if the shell supports balloon tips on taskbar icons. *balloonclickevent Occurs when user clicks on balloon tip. *balloonshowevent Occurs when the BalloonTip is shown. Balloon tips are queued by the system, so you balloon tip may not be displayed immediately after calling the DisplayBallloonTip method. *balloonhideevent Occurs if the user clicks on the close button of the balloon tip. *balloontimeoutevent Occurs if the system closes the balloon tip due to reaching the timeout. *iconrightclickevent Occurs when the user right-clicks on the icon in the Taskbar Notification Area ("systray"). *iconmiddleclickevent Occurs when the user clicks the icon using the middle mouse button. *icondblclickevent Occurs when the user double-clicks on the icon in the Taskbar Notification Area. *iconfile_assign *showballoontip Displays a balloon tip over the icon in the Taskbar Notification Area. Parameters: cBalloonText [, cBalloonTitle [, nIcon [, nTimeoutInSeconds]]] systray hyperlink1ClassPixelsiconidentifier^ iconcount^ enabled^ nprevmwaccrual^ integertostring^ loadicon^ aiconlist^ tiptext_assign^ stringtointeger^ iconfile_assign^ GoBack^ GoForward^ NavigateTo^ systray hyperlink hyperlinksystrayHeight = 23 Width = 23 tiptext = menutext = iconidentifier = 0 iconcount = 0 systrayiconid = 0 currenticonindex = 0 iconfile = menuoffsetfromright = 200 addicontosystrayatinit = .T. shellversion = 0 nprevmwaccrual = 0 Name = "systray" }pPROCEDURE receiveiconevent LPARAMETERS nDirection, nShift, nXCoord, nYCoord * This is the procedure that receives the Notify Icon events. Since * we execute the BindEvent() function from within this class, this * procedure can be hidden. * nDirection contains our icon identifier. IF nDirection != this.SystrayIconID * Not our icon, or this a real MouseWheel event on the VFP window. RETURN .F. ENDIF * These are the events that we receive from the Taskbar icon. #DEFINE WM_MOUSEMOVE 0x0200 #DEFINE WM_LBUTTONDOWN 0x0201 #DEFINE WM_LBUTTONUP 0x0202 #DEFINE WM_LBUTTONDBLCLK 0x0203 #DEFINE WM_RBUTTONDOWN 0x0204 #DEFINE WM_RBUTTONUP 0x0205 #DEFINE WM_RBUTTONDBLCLK 0x0206 #DEFINE WM_MBUTTONDOWN 0x0207 #DEFINE WM_MBUTTONUP 0x0208 #DEFINE WM_MBUTTONDBLCLK 0x0209 * Mousewheel events also get passed, but are difficult to decipher. #define WM_CONTEXTMENU 0x007B && Same as RightClick, but used when && Version 5 events have been specified. && (See the DisplayBalloonTip method.) #define WM_USER 0x0400 #define NIN_SELECT WM_USER + 0 #define NINF_KEY 0x1 #define NIN_KEYSELECT BITOR(NIN_SELECT , NINF_KEY) * Balloon events supported on Windows ME and Windows XP, and later. Not supported on Win2k. #DEFINE NIN_BALLOONSHOW (WM_USER + 2) #DEFINE NIN_BALLOONHIDE (WM_USER + 3) #DEFINE NIN_BALLOONTIMEOUT (WM_USER + 4) #DEFINE NIN_BALLOONUSERCLICK (WM_USER + 5) * Even though this isn't a real MouseWheel event on the VFP window, * VFP gives us the coordinates with respect to the location of the * main VFP window. Must change back to global coordinates. nXCoord = nXCoord + _screen.Left IF nXCoord <> WM_MOUSEMOVE && Ignore mousemove events. * The Version 5 events are more difficult to deal with, * but Version 5 supports Balloon Tips. So the extra work * is worth it. IF THIS.ShellVersion >= 5 DO CASE CASE nXCoord = NIN_SELECT OR nXCoord = NIN_KEYSELECT THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_CONTEXTMENU && Shell version 5 and later THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN CASE nXCoord = NIN_BALLOONSHOW THIS.BalloonShowEvent RETURN CASE nXCoord = NIN_BALLOONHIDE THIS.BalloonHideEvent RETURN CASE nXCoord = NIN_BALLOONTIMEOUT THIS.BalloonTimeoutEvent RETURN CASE nXCoord = NIN_BALLOONUSERCLICK This.BalloonClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ELSE * Using Version 4 events. DO CASE CASE nXCoord = WM_LBUTTONDOWN THIS.IconClickEvent CASE nXCoord = WM_LBUTTONDBLCLK ; OR nXCoord = WM_RBUTTONDBLCLK ; OR nXCoord = WM_MBUTTONDBLCLK * We'll just use one DoubleClick event, and ignore what button was used. THIS.IconDblClickEvent RETURN CASE nXCoord = WM_RBUTTONDOWN THIS.IconRightClickEvent RETURN CASE nXCoord = WM_MBUTTONDOWN THIS.IconMiddleClickEvent RETURN OTHERWISE * Unknown event. Just ignore it. RETURN ENDCASE ENDIF ENDIF ENDPROC PROCEDURE setforegroundwindow LPARAMETERS lhWnd * Brings the specified top-level window to the top of the Windows z-order. * If no parameter specified, then THISFORM is made the foreground window. IF TYPE("m.lhWnd") = "N" DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(m.lhWnd) RETURN ENDIF RETURN * If we're here, then the user called this method without an * hWnd parameter. Assume it is thisform that is to be brought forward. IF VARTYPE(thisform) == "O" IF ! thisform.Visible thisform.Visible = .t. ENDIF * First make sure it isn't minimized. IF thisform.WindowState= 1 thisform.WindowState = 0 ENDIF * If necessary, make sure the main VFP window is not minimized. IF _screen.Visible AND thisform.ShowWindow = 0 IF _screen.WindowState = 1 _screen.WindowState = 0 && Set back to normal ENDIF ENDIF DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd WinAPI_SetForegroundWindow(thisform.HWnd) ENDIF ENDPROC PROCEDURE cleariconlist * Removes all icons from the icon list. * In detail: * Releases the resource handles of all icons that have been loaded, * clears the array and sets the count to zero. This method is called * by the Destroy event, to make sure all system resources are correctly * released. WITH this IF .IconCount = 0 * No icons in list RETURN ENDIF * Have to release all icon resources DECLARE INTEGER DestroyIcon IN user32.dll AS WinAPI_DestroyIcon ; INTEGER hIcon FOR nCurrentIcon = 1 TO ALEN(.aIconList,1) WinAPI_DestroyIcon(.aIconList[m.nCurrentIcon]) ENDFOR DIMENSION .aIconList[1] .IconCount = 0 .CurrentIconIndex = 0 ENDWITH ENDPROC PROCEDURE addicontoiconlist LPARAMETERS cIconFileName IF !FILE(cIconFileName) RETURN .f. ENDIF * We must load the icon as a Windows resource. nHandle = THIS.LoadIcon(m.cIconFileName) IF m.nHandle = 0 RETURN .f. ENDIF * We have a resource handle for the icon. Now we store that * handle (integer) in our array of icon resource handles. WITH THIS .IconCount = .IconCount + 1 DIMENSION .aIconList[.IconCount] .aIconList[.IconCount] = m.nHandle IF .CurrentIconIndex = 0 .CurrentIconIndex = 1 && Updates Systray immediately ENDIF ENDWITH RETURN .T. ENDPROC PROCEDURE switchicon * Switches to the next icon in the iconlist. * Typically called by a timer to regularly change the icon, * to create animation. WITH THIS IF .IconCount > 1 IF .CurrentIconIndex = .iconCount .CurrentIconIndex = 1 ELSE .CurrentIconIndex = .CurrentIconIndex + 1 ENDIF ENDIF RETURN .CurrentIconIndex ENDWITH ENDPROC PROCEDURE addicontosystray *!* This is where the magic happens. *!* This is an unabashed hack to allow us to get the full functionality *!* of the Taskbar Notification Area ("System Tray") without having to *!* use an external C++ library. *!* To communicate with the systray, we tell it to send us messages via *!* the MouseWheel event. (This is the only VFP event that doesn't alter *!* or discard event data before firing the corresponding internal event.) *!* The second trick is that only the main VFP window will accept the events *!* without checking to see if the event coordinates are invalid. (The screen *!* doesn't even need to be visible, so you can have SCREEN=OFF in your *!* config.fpw file.) So we use VFP8's BINDEVENT() function to bind to the *!* _SCREEN.MouseWheel event. *!* This method sets up that communication path. *!* Returns 1 if successful. LOCAL lcNotifyIconData ** NOTIFYICON struct defines #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 && Win2k and later. #define NIF_INFO 0x00000010 && Use balloon tip. Win2k and later. && Notify Icon Infotip flags #define NIIF_NONE 0x00000000 && icon flags are mutually exclusive && and take only the lowest 2 bits #define NIIF_INFO 0x00000001 #define NIIF_WARNING 0x00000002 #define NIIF_ERROR 0x00000003 #define NIIF_ICON_MASK 0x0000000F #define NIIF_NOSOUND 0x00000010 && Windows XP and later. #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIM_SETFOCUS 0x00000003 && Windows 2000 and later. #define NIM_SETVERSION 0x00000004 && Windows 2000 and later. #define NOTIFYICON_VERSION 3 #define NIS_HIDDEN 0x00000001 #define NIS_SHAREDICON 0x00000002 WITH THIS * If no icons loaded, do nothing. IF .IconCount < 1 OR .CurrentIconIndex = 0 RETURN 0 ENDIF IF NOT .Enabled * Each SystemTray icon in this process requires a unique ID. We'll * use an _screen property to make sure each instance of this class * gets its own ID. Properties on _screen aren't affected by CLEAR ALL. * Because VFP drops the least significant two bytes of the mousewheel event's * WPARAM parameter, our unique SysTray Icon ID must use the most significant * two bytes: #DEFINE MIN_SYSTRAY_ICON_ID 0x4000 IF TYPE("_screen.nSysTrayCount") == "U" * We're the first one here. _screen.AddProperty("nSysTrayCount", 0) ELSE IF _Screen.nSysTrayCount > 0x3FFF * User is apparently creating and destroying this object repeatedly. _Screen.nSysTrayCount = 0 ENDIF ENDIF _screen.nSysTrayCount = _screen.nSysTrayCount + 1 this.SystrayIconID = _screen.nSysTrayCount + MIN_SYSTRAY_ICON_ID * By default, VFP combines MouseWheel events. (That is, if we receive * a MouseWheel event from the OS, we check our internal queue to see * if there is already a MouseWheel event that hasn't been processed yet. * If found, we just add the number of MouseWheel turns to the existing * event.) This behavior makes our communication with the SystemTray * unstable, so we must disable this combining of events. SYS(2060) * accomplishes this: THIS.nPrevMWAccrual = VAL(SYS(2060)) SYS(2060, 1) ENDIF * Declare the WinAPI function that lets us install the icon. * We redeclare the function every time, just in case CLEAR DLLS is called * elsewhere in the app. DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; STRING @ PNOTIFYICONDATA IF THIS.ShellVersion = 0 && If we haven't set the version yet. nTipTextMaxLength = 63 && Default to version 4. ELSE nTipTextMaxLength = 127 ENDIF * Build NOTIFYICONDATA structure. lcNotifyIconData = .IntegerToString(_VFP.Hwnd) && Messages get sent to VFP's main window. lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), nTipTextMaxLength ), nTipTextMaxLength + 1, CHR(0)) && TipText IF .ShellVersion >= 5 lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags ENDIF lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure IF NOT .Enabled && If not already in Taskbar Notification Area.... * Add icon to Taskbar Notification Area. nReturn = WinAPI_Shell_NotifyIcon( NIM_ADD, @lcNotifyIconData) IF nReturn <> 1 * Adding item to tray failed!!! IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, .nPrevMWAccrual) && Reset mousewheel behavior. ENDIF RETURN nReturn ENDIF * Bind to the MouseWheel event on VFP's main window. nReturn = BINDEVENT(_screen,"MouseWheel",this,"receiveIconEvent",2) .Enabled = .t. * We've just added the icon. Now we see if we can set to Version 5 shell behavior, * with larger tooltip lengths and support for balloon Tips. IF .GetShellVersion() >= 5 * This OS supports version 5 features. Switch to version 5. * We have to inform the Shell that we want to use Version 5 features. * Build a version 5 structure for sending message to change version. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * First we check to see if we can switch to using version 5 events. nReturn = WinAPI_Shell_NotifyIcon( NIM_SETVERSION, @lcNotifyIconData) IF nReturn <> 1 * Couldn't switch to version 5 events RETURN -1 ENDIF .ShellVersion = 5 * Now that we've updated to Version 5, we have to modify the existing icon * if the .TipText is longer than 63 characters. IF LEN(.TipText) > 63 nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 RETURN nReturn ENDIF ENDIF ELSE .ShellVersion = 4 ENDIF ELSE * Already in the Taskbar Notification Area. Just update existing icon. nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) IF nReturn <> 1 MESSAGEBOX(" * Modifying item in tray failed!!!") RETURN nReturn ENDIF ENDIF ENDWITH RETURN nReturn ENDPROC PROCEDURE removeiconfromsystray * Removes the icon from the Taskbar Notification Area ('System Tray'). DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ; INTEGER dwMessage, ; string @ PNOTIFYICONDATA #define NIM_ADD 0x00000000 #define NIM_MODIFY 0x00000001 #define NIM_DELETE 0x00000002 #define NIF_MESSAGE 0x00000001 #define NIF_ICON 0x00000002 #define NIF_TIP 0x00000004 #define NIF_STATE 0x00000008 #define NIF_INFO 0x00000010 LOCAL cNotifyIconData WITH THIS UNBINDEVENTS(_screen, "MouseWheel", this, "receiveIconEvent") .Enabled = .f. * Build structure. cNotifyIconData = .IntegerToString(_vfp.hwnd) cNotifyIconData = cNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) cNotifyIconData = cNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON)) &&NIF_TIP,NIF_INFO,NIF_STATE, ,NIF_TIP cNotifyIconData = cNotifyIconData + .IntegerToString(0x20A) && uCallback cNotifyIconData = cNotifyIconData + .IntegerToString(0) && icon handle cNotifyIconData = cNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 63), 64, CHR(0)) cNotifyIconData = .integertostring(LEN(cNotifyIconData) + 4) + cNotifyIconData ENDWITH nReturn = WinAPI_Shell_NotifyIcon(NIM_DELETE, @cNotifyIconData) THIS.ShellVersion = 0 IF _Screen.nSysTrayCount = 1 * There has not been two simultaneous instances of this object * in the current session. We can clean up after ourselves. _Screen.nSysTrayCount = 0 SYS(2060, THIS.nPrevMWAccrual) && Reset mousewheel behavior. ENDIF ENDPROC PROCEDURE showmenu LPARAMETERS lcMPRFileName, p1, p2, p3, p4, p5, p6 * Displays the shortcut menu near the taskbar icon. * If the menu is defined as a set of tokens and text strings, the * menu is defined here. Otherwise, the specified MPR is called. * If the first parameter is blank or not specified, the contents * of the MenuText property are used to define/determine the menu. * If the first parameter is not blank, it is assumed to specify * the MPR to run. The MenuText property is ignored. * * The additional parameters are passed on to the MPR. They are only * used if the cMPRFileName parameter is specified and non-blank. * * In order to display the shortcut menu in the proper location, * we must activate it within a toplevel form (with .visible = .t.), * near the taskbar. * On WindowsME, Win2k and later, the form is made invisible by * calling a Windows API function. (See the INIT of the form class.) * But on Win98 and NT4, that API function didn't exist, so we make it * a "Desktop" form instead. This allows us to remove the titlebar and * make the form so small it won't be noticed. But it causes slightly * incorrect behavior when trying to clear the menu. LOCAL lhPreviousWindow, lcPOINT, lnReturn, lnXCoord, lnYCoord, lcClassLib, llContinue LOCAL lnTaskBarLeft, lnTaskBarTop, lnTaskBarRight, lnTaskBarBottom, lnTaskBarLocation LOCAL loTempForm as Form * Our invisible form must be made the top window in order to make the shortcut * menu get cleared properly if user clicks elsewhere. Therefore, we store the * handle of the current foreground window, so we can restore it later. DECLARE INTEGER GetForegroundWindow IN user32.dll AS WinAPI_GetForegroundWindow lhPreviousWindow = WinAPI_GetForegroundWindow() * Get location of the mouse pointer. DECLARE INTEGER GetCursorPos IN user32.dll AS WinAPI_GetCursorPos STRING @ lcPOINT = REPLICATE(CHR(0), 8) lnReturn = WinAPI_GetCursorPos(@lcPOINT) lnXCoord = THIS.StringToInteger(LEFT(m.lcPOINT,4)) lnYCoord = THIS.StringToInteger(SUBSTR(m.lcPOINT, 5)) * Get the position and coordinates of the taskbar. lnTaskBarLeft = 0 lnTaskBarRight = 0 lnTaskBarTop = 0 lnTaskBarBottom = 0 lnTaskBarLocation = THIS.GetTaskBarPosition(@lnTaskBarLeft, @lnTaskBarTop, @lnTaskBarRight, @lnTaskBarBottom) * Need Top-Level form. * It also has ShowInTaskBar=.F. so we don't get a large button on the taskbar. * It is also invisible, using the SetWindowLong API function in the form's INIT. lcClassLib = "systray.vcx" llContinue = .T. DO WHILE llContinue TRY loTempForm = NEWOBJECT("x_frmInternalSystrayUseOnly", lcClassLib ) llContinue = .F. CATCH IF lcClassLib = "systray.vcx" * Try to create the form by looking in the same place as this object lcClassLib = ADDBS(JUSTPATH(SYS(1271, this))) + "systray.vcx" ELSE loTempForm = .Null. llContinue = .F. ENDIF ENDTRY ENDDO * Now position form near mouse, but adjust for taskbar. DO CASE CASE m.lnTaskBarLocation = 1 && Left loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarRight + 7 CASE m.lnTaskBarLocation = 2 && Top * Ideally would determine height of titlebar, and subtract that. * But this is sufficient. loTempForm.top = m.lnTaskBarBottom - 8 loTempForm.Left = m.lnXCoord CASE m.lnTaskBarLocation = 3 && Right loTempForm.top = m.lnYCoord loTempForm.Left = m.lnTaskBarLeft - this.MenuOffsetFromRight && Roughly the width of a typical menu. OTHERWISE && Bottom, or coding error. * Just place the window near the mouse. loTempForm.top = m.lnYCoord loTempForm.Left = m.lnXCoord ENDCASE * Window must be the foreground window when shortcut menu displayed. Otherwise, * clicking in another window won't release the shortcut menu. loTempForm.Show THIS.SetForegroundWindow(loTempForm.hWnd) * Now display the menu. DO CASE CASE (PCOUNT() > 0 AND TYPE("m.lcMPRFileName") = "C") * Want to run the MPR specified in parameter IF PCOUNT() = 1 DO (m.lcMPRFileName) RELEASE loTempForm ELSE * Build parameter list LOCAL lcParamList, lnCount lcParamList = "p1" FOR nCount = 3 TO PCOUNT() && if PCOUNT() is only 2, don't enter loop. lcParamList = m.lcParamList + ", p" + TRANSFORM(m.lnCount - 1) ENDFOR DO (m.lcMPRFileName) WITH &lcParamList RELEASE loTempForm ENDIF CASE THIS.MenuTextIsMPR AND FILE(THIS.MenuText) * We want to run the MPR specified in MenuText property. DO (THIS.MenuText) RELEASE loTempForm OTHERWISE * Build menu from this.MenuText. LOCAL lnMenuItems, lcMenuName, laMenuItems[1], lnCurrentMenuItem LOCAL lnToken, lcText, lnBarNum lnMenuItems = ALINES(m.laMenuItems, THIS.MenuText, .T., ";") IF m.lnMenuItems = 0 RELEASE loTempForm RETURN .f. ENDIF IF m.lnMenuItems % 2 != 0 * Incorrect number of items in MenuText property. IF m.lnMenuItems > 1 lnMenuItems = lnMenuItems - 1 ELSE RELEASE loTempForm RETURN .f. ENDIF ENDIF lcMenuName = SYS(2015) * Make private variable to receive the ID of the selected menu item. PRIVATE __systray_menu_return __systray_menu_return = 0 * Build the menu. DEFINE POPUP (m.lcMenuName) SHORTCUT RELATIVE FROM MROW(),MCOL() FOR lnCurrentMenuItem = 1 TO m.lnMenuItems STEP 2 lnToken = VAL(m.laMenuItems[m.lnCurrentMenuItem]) lcText = m.laMenuItems[m.lnCurrentMenuItem + 1] lnBarNum = FLOOR(m.lnCurrentMenuItem/2) + 1 DEFINE BAR (lnBarNum) OF (m.lcMenuName) PROMPT (m.lcText) lcOnSelectionBar = "ON SELECTION BAR " + TRANSFORM(m.lnBarNum) + " OF " + ; TRANSFORM(m.lcMenuName) + " __systray_menu_return = " + TRANSFORM(m.lnToken) &lcOnSelectionBar ENDFOR SET ESCAPE off * Display the menu and wait for user action. ACTIVATE POPUP (m.lcMenuName) RELEASE loTempForm THIS.ProcessMenuEvent(__systray_menu_return) RETURN __systray_menu_return ENDCASE * Menu has been deactivated. Restore previous foreground window. This.SetForegroundWindow(m.lhPreviousWindow) ENDPROC PROCEDURE processmenuevent LPARAMETERS nMenuItemID * This event is fired after a menu has been clicked or cleared. ENDPROC PROCEDURE iconclickevent * This "event" is called when the user left-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE integertostring LPARAMETERS nInteger * Changes an integer into the Intel representation of that integer. * Used to build structures. RETURN CHR(BITAND(nInteger, 255)) + CHR(BITAND(BITRSHIFT(nInteger, 8), 255)) + ; CHR(BITAND(BITRSHIFT(nInteger, 16), 255)) + CHR(BITAND(BITRSHIFT(nInteger, 24), 255)) ENDPROC PROCEDURE loadicon LPARAMETERS cIconFileName * Loads an icon as a Windows resource. Returns the resource handle. LOCAL cOldTalk, cTempFileName, hImage DECLARE INTEGER LoadImage IN user32.dll AS WinAPI_LoadImage ; INTEGER hinst, ; STRING lpszName, ; INTEGER uType, ; && uint INTEGER cxDesired, ; INTEGER cyDesired, ; INTEGER fuLoad && uint ** LoadImage defines **** #define IMAGE_ICON 1 #define LR_LOADFROMFILE 0x0010 * Assume that the icon is contained in the app/exe file. Copy to temp before loading. IF FILE(cIconFileName) cTempFileName = ADDBS(GETENV("temp")) + "systray_temp_" + SYS(3) + ".icn" COPY FILE (cIconFileName) TO (cTempFileName) ELSE RETURN ENDIF m.hImage = WinAPI_LoadImage( 0, ; && Don't load from resource file cTempFileName, ; IMAGE_ICON, ; 0, ; 0, ; LR_LOADFROMFILE) * Now need to erase the temp file. SET TALK OFF to avoid the "File has been erased" * message on the status bar. cOldTalk = SET("TALK") ERASE (cTempFileName) SET TALK &cOldTalk IF m.hImage = 0 this.Error(0, "Failed to load icon '" + cIconFileName + "'. in " + PROGRAM(), LINENO()) ENDIF RETURN m.hImage ENDPROC PROCEDURE tiptext_assign LPARAMETERS vNewVal * The text for the tooltip is being changed. THIS.tiptext = ALLTRIM(m.vNewVal) IF THIS.Enabled * This icon is already displayed in the Taskbar Notification Area, * so we must update the systray immediately: this.AddIconToSystray() endif ENDPROC PROCEDURE currenticonindex_assign LPARAMETERS vNewVal * This is called when user changes the CurrentIconIndex property. THIS.currenticonindex = m.vNewVal IF this.Enabled * We're attached to systray. Update immediately. this.AddIconToSystray ENDIF ENDPROC PROCEDURE stringtointeger LPARAMETERS lcPDWORD, lnBytes * Converts a string from the Intel int, DWORD,WORD, and BYTE formats * into a VFP integer. IF PCOUNT() < 2 lnBytes = 4 && No length provided, assume 4-byte integer. ENDIF LOCAL lnReturn, lnCurByte lnReturn = 0 FOR lnCurByte = 1 to m.lnBytes lnReturn = m.lnReturn + ASC(SUBSTR(m.lcPDWord, m.lnCurByte, 1))*(256^(m.lnCurByte-1)) ENDFOR RETURN lnReturn ENDPROC PROCEDURE gettaskbarposition LPARAMETERS nLeft, nTop, nRight, nBottom * We position any menus near where the mouse just clicked. But we need to * fine-tune that position depending on which edge of the screen the * user has docked the taskbar. This method retrieves the absolute * coordinates of the taskbar, and derives from those coordinates which * edge the taskbar is on. #DEFINE ABM_GETTASKBARPOS 0x00000005 DECLARE INTEGER SHAppBarMessage IN shell32.dll as WinAPI_SHAppBarMessage ; INTEGER dwMessage, STRING @ PAPPBARDATA LOCAL cAPPBARDATA, nReturn WITH THIS cAPPBARDATA = .IntegerToString(0) && hWnd cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && uCallBackMsg cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && Edge cAPPBARDATA = cAPPBARDATA + REPLICATE(CHR(0), 4*4) && RECT structure cAPPBARDATA = cAPPBARDATA + .IntegerToString(0) && lParam cAPPBARDATA = .IntegerToString(LEN(cAPPBARDATA) + 4) + cAPPBARDATA nReturn = WinAPI_SHAppBarMessage(ABM_GETTASKBARPOS, @cAPPBARDATA) * Taskbar coordinates should be in RECT structure. nLeft = .StringToInteger(SUBSTR(cAPPBARDATA, 17, 4)) IF nLeft > 0xFFFFFF && If the number is ridiculously large, assume it was a negative number. nLeft = 0 - (0xFFFFFFFF - nLeft) ENDIF nTop = .StringToInteger(SUBSTR(cAPPBARDATA, 21, 4)) IF nTop > 0xFFFFFF nTop = 0 - (0xFFFFFFFF - nTop) ENDIF nRight = .StringToInteger(SUBSTR(cAPPBARDATA, 25, 4)) IF nRight > 0xFFFFFF nRight = 0 - (0xFFFFFFFF - nRight) ENDIF nBottom = .StringToInteger(SUBSTR(cAPPBARDATA, 29, 4)) IF nBottom > 0xFFFFFF nBottom = 0 - (0xFFFFFFFF - nBottom) ENDIF ENDWITH DO CASE * Must use approximations for taskbar coordinates, because they oare slightly different * on different versions of Windows. CASE nLeft < 2 AND nTop < 2 AND nRight < 479 && Roughly minimum width in portrait mode. RETURN 1 && Left CASE nLeft < 2 AND nTop > 0 RETURN 0 && Bottom CASE nLeft < 2 AND nTop < 2 && Must be top, otherwise first case would take it. RETURN 2 && Top OTHERWISE RETURN 3 && Right ENDCASE *!* typedef struct _AppBarData { *!* DWORD cbSize; *!* HWND hWnd; *!* UINT uCallbackMessage; *!* UINT uEdge; *!* RECT rc; *!* LPARAM lParam; *!* } APPBARDATA, *PAPPBARDATA *!* typedef struct _RECT { *!* LONG left; *!* LONG top; *!* LONG right; *!* LONG bottom; *!* } RECT ENDPROC PROCEDURE getshellversion DECLARE INTEGER DllGetVersion IN shell32.dll AS WinAPI_Shell32_DllGetVersion ; STRING @ DLLVERSIONINFO * Windows 98 typically returns 4.72, depending on version of IE installed. * Windows ME returns 5.5 or greater. * Windows 2000 returns 5.0 * Windows XP returns 6.0 LOCAL lcDLLVersionInfo, lnMajor, lnMinor WITH THIS lcDLLVersionInfo = .IntegerToString(20) lcDLLVersionInfo = m.lcDLLVersionInfo + REPLICATE(CHR(0), 16) WinAPI_Shell32_DllGetVersion(@lcDLLVersionInfo) lnMajor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 5, 4)) lnMinor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 9, 4)) RETURN VAL(STR(m.lnMajor, 2, 0) + "." + ALLTRIM(STR(m.lnMinor,3,0))) ENDWITH ENDPROC PROCEDURE iconrightclickevent * This "event" is called when the user right-clicks on the icon. * Default behavior is to display the menu: IF NOT ISBLANK(THIS.MenuText) THIS.ShowMenu() && The ShowMenu method will handle any errors. ENDIF ENDPROC PROCEDURE iconfile_assign LPARAMETERS vNewVal *To do: Modify this routine for the Assign method THIS.ICONFILE = m.vNewVal * Updating this property will release all other icons. THIS.ClearIconList() THIS.AddIconToIconList(m.vNewVal) && Going from 0 to 1 icon updates systray immediately. ENDPROC PROCEDURE showballoontip LPARAMETERS lcBalloonText, lcBalloonTitle, lnIcon, lnTimeout * Displays a balloon tip if the Operating System shell supports it. * The lnTimeout parameter is the number of SECONDS to display the balloon. This * timeout period is subject to minimum and maximum values set by the operating * system (typically 10 and 30 seconds). * The lnIcon parameter specifies the icon to display in the * balloon. The values are as follows: * 0 = No icon * 1 = Info. A lowercase "i" in a small balloon * 2 = Warning. An exclamation point (!) in a triangle. * 3 = Error. A red disk with an X through it. * Add 16 (0x10) to the value to mute the sound that is played when * the balloon is displayed (Windows XP and later). * This method uses #DEFINES that are set in the AddIconToSystray method. IF PCOUNT() < 1 * Must at least include the balloon Text. RETURN -1 ENDIF IF THIS.ShellVersion < 5 &&GetShellVersion() < 5.00 * This shell doesn't support balloon tips. RETURN -1 ENDIF IF TYPE("m.lcBalloonText") <> "C" RETURN -1 ENDIF IF TYPE("m.lcBalloonTitle") <> "C" lcBalloonTitle = "" ENDIF IF TYPE("m.lnIcon") <> "N" lnIcon = 0 ENDIF IF TYPE("m.lnTimeout") <> "N" lnTimeout = 0 ELSE lnTimeOut = m.lnTimeout * 1000 && Convert seconds to Milliseconds. ENDIF LOCAL lcNotifyIconData, lnReturn WITH THIS * Build NOTIFYICONDATA structure compatible with version 5 of the shell. lcNotifyIconData = .IntegerToString(_vfp.hwnd) lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000) lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON, NIF_INFO)) &&NIF_TIP,NIF_INFO,NIF_STATE lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A) && uCallback lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex]) && icon handle lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0)) && TipText lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwState lcNotifyIconData = lcNotifyIconData + .IntegerToString(0) && dwStateMask lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonText), 255),256, CHR(0)) && balloon tip. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(m.lnTimeOut) && Timeout/Version lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonTitle), 63),64, CHR(0)) && balloon tip title. lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(lnIcon, 0x1F)) && Info flags lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData && length of structure * Send request to display balloon. This is asynchronous, so * balloon may not be displayed immediately. lnReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData) ENDWITH ENDPROC PROCEDURE Destroy WITH THIS IF .Enabled .RemoveIconFromSystray() .ClearIconList() && Release resources. ENDIF ENDWITH ENDPROC PROCEDURE Init * If there is an initial icon specified, add it to the Icon List. IF TYPE("THIS.IconFile") = "C" AND LEN(THIS.IconFile) > 0 THIS.AddIconToIconList(THIS.IconFile) ENDIF * Add icon to systray if requested. IF this.AddIconToSystrayAtInit this.AddIconToSystray ENDIF ENDPROC 2 v2v2X%*0,Uq%.B-T9%j% Hx$ C  *       B {  B   B =  B ]  B } B  B2Bf Hb  *       B 6  B V  B2bBU NDIRECTIONNSHIFTNXCOORDNYCOORDTHIS SYSTRAYICONIDLEFT SHELLVERSIONICONCLICKEVENTICONDBLCLICKEVENTICONRIGHTCLICKEVENTICONMIDDLECLICKEVENTBALLOONSHOWEVENTBALLOONHIDEEVENTBALLOONTIMEOUTEVENTBALLOONCLICKEVENT%Cm.lhWndbN~H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC BB%CO% Ta%T%9  )%9%T9H|SetForegroundWindow user32.dllQWinAPI_SetForegroundWindowC U LHWNDSETFOREGROUNDWINDOWUSER32DLLWINAPI_SETFOREGROUNDWINDOWTHISFORMVISIBLE WINDOWSTATE SHOWWINDOWHWND %#B8| DestroyIcon user32.dllQWinAPI_DestroyIcon(CCC TTU THIS ICONCOUNT DESTROYICONUSER32DLLWINAPI_DESTROYICON NCURRENTICON AICONLISTCURRENTICONINDEX%C0 B-TC % SB- TT %TBaU CICONFILENAMENHANDLETHISLOADICON ICONCOUNT AICONLISTCURRENTICONINDEXt m%_%@T[T BUTHIS ICONCOUNTCURRENTICONINDEXN >%  ; B% )%C_screen.nSysTrayCountbUC nSysTrayCount9%9?T9T99T9@TCC ]gC ]F|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon%  T? TTCCTCTCC   TC TCC'TCCC_=C % TCTCTCC QTCTCC @QTCCTCC>% TC  %}%9pT9C ] B5TC9 MouseWheelreceiveIconEvent Ta%CTCCTCTCC   TC TCC#TCCC_=C TCTCTCC QTCTCC @QTCCTCC>TC  %W BT %C>?TC  % BT :TC  %61C% * Modifying item in tray failed!!!x B BULCNOTIFYICONDATATHIS ICONCOUNTCURRENTICONINDEXENABLED ADDPROPERTY NSYSTRAYCOUNT SYSTRAYICONIDNPREVMWACCRUALSHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICON SHELLVERSIONNTIPTEXTMAXLENGTHINTEGERTOSTRINGHWND AICONLISTTIPTEXTNRETURNGETSHELLVERSIONF|Shell_NotifyIcon shell32.dllQWinAPI_Shell_NotifyIcon Q.C9 MouseWheelreceiveIconEvent T-TCCTC TCC   TC TC#TCCC _?=@C TCC>T C T %9 T9 C ]USHELL_NOTIFYICONSHELL32DLLWINAPI_SHELL_NOTIFYICONCNOTIFYICONDATATHISENABLEDINTEGERTOSTRINGHWND SYSTRAYICONIDTIPTEXTNRETURN SHELLVERSION NSYSTRAYCOUNTNPREVMWACCRUAL     QFORMG|GetForegroundWindow user32.dllQWinAPI_GetForegroundWindowTC;| GetCursorPos user32.dllQWinAPI_GetCursorPosTCC QT CT CC =T CC \ T T T T!TCT  systray.vcx T a+ .TCx_frmInternalSystrayUseOnly T -% systray.vcxc(T CCC] systray.vcx T T - Hz T T  T T  NT T 2zT T   C"! H/ CtCm.lcMPRFileNamebC %Ct  < #$T#p1%(CtV"T# #, pC $_*DO (m.lcMPRFileName) WITH &lcParamList < & C'0  '<2()*+,-.T(C *'a;% (/<B-%C (G% (oT((<B-T)C]5/ T/s )CCW+( (T,CC + *gT-C + *T.C + zs. )" -[T0ON SELECTION BAR C ._ OF C )_ __systray_menu_return = C ,_&lcOnSelectionBar G t )<C/1 B/C !U2 LCMPRFILENAMEP1P2P3P4P5P6LHPREVIOUSWINDOWLCPOINTLNRETURNLNXCOORDLNYCOORD LCCLASSLIB LLCONTINUE LNTASKBARLEFT LNTASKBARTOPLNTASKBARRIGHTLNTASKBARBOTTOMLNTASKBARLOCATION LOTEMPFORMGETFOREGROUNDWINDOWUSER32DLLWINAPI_GETFOREGROUNDWINDOW GETCURSORPOSWINAPI_GETCURSORPOSTHISSTRINGTOINTEGERGETTASKBARPOSITIONTOPLEFTMENUOFFSETFROMRIGHTSHOWSETFOREGROUNDWINDOWHWND LCPARAMLISTLNCOUNTNCOUNT MENUTEXTISMPRMENUTEXT LNMENUITEMS LCMENUNAME LAMENUITEMSLNCURRENTMENUITEMLNTOKENLCTEXTLNBARNUM__SYSTRAY_MENU_RETURNLCONSELECTIONBARPROCESSMENUEVENT U NMENUITEMID(%CZ ! CUTHISMENUTEXTSHOWMENUQGBCC CCC CCC CCC UNINTEGERt>| LoadImage user32.dllQWinAPI_LoadImage%C05TCCtemp5 systray_temp_C].icn(B#T CTCTALKv SET TALK &cOldTalk % b?CFailed to load icon ''. in Ct   B U CICONFILENAMECOLDTALK CTEMPFILENAMEHIMAGE LOADIMAGEUSER32DLLWINAPI_LOADIMAGETHISERROR?TC %8 CUVNEWVALTHISTIPTEXTENABLEDADDICONTOSYSTRAY:T %3 UVNEWVALTHISCURRENTICONINDEXENABLEDADDICONTOSYSTRAY %Ct ) T  T( 2T CC \  BULCPDWORDLNBYTESLNRETURN LNCURBYTED|SHAppBarMessage shell32.dllQWinAPI_SHAppBarMessage  LTC TC TC TCC QTC TCC> T C TCC\ %XT ATCC\ %T ATCC\ %T ATCC\ %HT A H](     B    B    B2 BU NLEFTNTOPNRIGHTNBOTTOMSHAPPBARMESSAGESHELL32DLLWINAPI_SHAPPBARMESSAGE CAPPBARDATANRETURNTHISINTEGERTOSTRINGSTRINGTOINTEGERF| DllGetVersion shell32.dllQWinAPI_Shell32_DllGetVersion TCT CC QCTCC \ TCC  \ *BCC Z.CC ZgU DLLGETVERSIONSHELL32DLLWINAPI_SHELL32_DLLGETVERSIONLCDLLVERSIONINFOLNMAJORLNMINORTHISINTEGERTOSTRINGSTRINGTOINTEGER(%CZ ! CUTHISMENUTEXTSHOWMENU<T CC UVNEWVALTHISICONFILE CLEARICONLISTADDICONTOICONLIST%Ct . B% P B#%Cm.lcBalloonTextbC B$%Cm.lcBalloonTitlebC T%Cm.lnIconbN T%C m.lnTimeoutbN T/T   TCC TC "TCC    TC TCC #TCCC _=C TCTC%TCCC _=C TC $TCCC _?=@C TCCTCC>TC U LCBALLOONTEXTLCBALLOONTITLELNICON LNTIMEOUTTHIS SHELLVERSIONLCNOTIFYICONDATALNRETURNINTEGERTOSTRINGHWND SYSTRAYICONID AICONLISTCURRENTICONINDEXTIPTEXTWINAPI_SHELL_NOTIFYICON< 5%1 C CUTHISENABLEDREMOVEICONFROMSYSTRAY CLEARICONLISTi1%C THIS.IconFilebC C> DC%b UTHISICONFILEADDICONTOICONLISTADDICONTOSYSTRAYATINITADDICONTOSYSTRAYreceiveiconevent,setforegroundwindow cleariconlistaddicontoiconlist switchiconaddicontosystrayremoveiconfromsystrayshowmenudprocessmenueventiconclickeventintegertostringCloadicontiptext_assigncurrenticonindex_assignstringtointegerqgettaskbarposition= getshellversion#iconrightclickeventT%iconfile_assign%showballoontip&Destroy)Init,*11FrA1"DAAA!A!A!A!A!ABA!A!A!ABAAB4qAABTACA1AAA5!BAQAA5qqAs1qA1q!AAr7!11AAA4qAAAAQqZAiBADqq!aaaAS2AAR4Eq1aaaSADQAAARAAB3f}Dqa1AR3!A3PquA!AAA1!a1c!1!"!A1qq!AqAq1qqA2qqAArQAarB32q4DA4qt4qQ!AA74a3A2qBA2q#A3A!A21K!aaaRAAAAAAAABA@1b!A3DA3q"131ABA2ABAAAAB!q1aaQAUA3AB41AA2, 7V lNN[vmx9#8O8r>> VK4VVMVWQWXTX]f]^l^_r`a}a4kZkn=;noU8oEp^jp{h{3|N|e})v2tiptext Text to be displayed in standard tooltip above the Taskbar Notification Area ("systray"). menutext Defines a menu to be displayed when user clicks on icon. Specify a numeric ID and text for each item, separating each by a semicolon. (e.g. 1;Activate; 2; Exit). The ID is passed to the ProcessMenuEvent method after the user has made a menu selecti menutextismpr Set to true if the MenuText property contains the name of a MPR to run. The MPR must define a shortcut menu. To pass parameters to the MPR, call ShowMenu method from the IconClick event. iconidentifier Used internally to store a unique id for our icon. This way, multiple instances of this class can be used without interfering with each other. iconcount systrayiconid Contains Unique ID for the systray token that this object creates. currenticonindex Determines which of the currently loaded icons is displayed in the Taskbar Notification Area ("systray"). enabled Is .T. when we have an icon in the Taskbar Notification Area ("systray"). iconfile If only one icon is used, it can be specified here. If multiple icons are to be used (animation), use the AddIconToIconList method. menuoffsetfromright Essentially the width of your menu in pixels. This is an offset used when the TaskBar is docked on the right, to prevent the menu from being drawn underneath the taskbar. addicontosystrayatinit If .T., the icon will be added to the Taskbar Notification Area ("systray") when this class is instantiated. If .F., you must call the AddIconToSystray() method. shellversion Contains the numeric version of the Shell communication method that we've established. nprevmwaccrual Previous setting of SYS(2060). *receiveiconevent Receives the events the Notify Icon. *setforegroundwindow By default, brings the form that contains this object to the foreground. You can also specify a different window to bring forward by passing a numeric hWnd parameter. *cleariconlist Clear all icons from the current list. If an icon is currently displayed in the taskbar notification area (systray), it is removed. *addicontoiconlist Adds an icon file to list of icons. Accepts a string parameter containing a filename. Call the SwitchIcon method to switch to the next icon. *switchicon Changes the displayed icon to the next icon in the icon list. Use AddIconToIconList method to populate the icon list, and ClearIconList to start over. *addicontosystray Adds the icon to the Taskbar Notification Area ("systray"). *removeiconfromsystray Removes the icon from the Taskbar Notification Area ("systray"). *showmenu Displays the menu defined by the MenuText property, or runs an MPR that defines a shortcut menu. Accepts string parameter containing the filename of the MPR to run, and optional second parameter to be passed to the MPR startup code. *processmenuevent This event is called if user has selected an item from a menu defined by the MenuText property. Add code here to act upon the user's selection. *iconclickevent Occurs when the user clicks on the icon in the Taskbar Notification Area ("systray"). *integertostring Converts an integer to its binary representation. *loadicon Loads an Icon from a file, and returns an hIcon token. ^aiconlist[1,0] Array containnig Icons and handles. *tiptext_assign *currenticonindex_assign *stringtointeger Convert binary representation of integer into an integer data type. *gettaskbarposition Retrieves coordinates of TaskBar. Also returns 0 if docked on bottom, 1 if docked left, 2 if docked top, 3 if docked right. *getshellversion Determines the Major and Minor version of shell32.dll. Used to determine if the shell supports balloon tips on taskbar icons. *balloonclickevent Occurs when user clicks on balloon tip. *balloonshowevent Occurs when the BalloonTip is shown. Balloon tips are queued by the system, so you balloon tip may not be displayed immediately after calling the DisplayBallloonTip method. *balloonhideevent Occurs if the user clicks on the close button of the balloon tip. *balloontimeoutevent Occurs if the system closes the balloon tip due to reaching the timeout. *iconrightclickevent Occurs when the user right-clicks on the icon in the Taskbar Notification Area ("systray"). *iconmiddleclickevent Occurs when the user clicks the icon using the middle mouse button. *icondblclickevent Occurs when the user double-clicks on the icon in the Taskbar Notification Area. *iconfile_assign *showballoontip Displays a balloon tip over the icon in the Taskbar Notification Area. Parameters: cBalloonText [, cBalloonTitle [, nIcon [, nTimeoutInSeconds]]] systray hyperlink1ClassPixelsiconidentifier^ iconcount^ enabled^ nprevmwaccrual^ integertostring^ loadicon^ aiconlist^ tiptext_assign^ stringtointeger^ iconfile_assign^ GoBack^ GoForward^ NavigateTo^ systray